代码之家  ›  专栏  ›  技术社区  ›  Alfons

对基本引用进行强制转换和复制是一种肮脏的黑客行为,但它到底有什么肮脏之处呢?

  •  2
  • Alfons  · 技术社区  · 7 年前

    问题:我有一个包含POD变量的相当大的结构,我需要复制一些字段,但不能复制其他字段。懒得写下一个成员一个成员的复制函数。

    解决方案:将可复制字段移动到基,分配基。这样地:

    struct A
    {
       int a, b, c;
    };
    
    struct B : public A
    {
        int d, e, f;
    };
    
    //And copy:
    
    B x, y;
    (A&)x = y; //copies the part of B that is A
    

    我知道,这很肮脏。我和同事们进行了一次热烈的讨论:这个准则,我的能力和我的道德品质。然而,我听到的最难的具体指控是“d,e,f在副本中没有初始化”。是的,我知道,这就是我的意图。我当然会在别处初始化它们。

    另一项指控是“不安全的打字”。但这是对基类的安全类型转换!就像是

    ((A*)&x)->operator=(b);
    

    但不那么冗长。这个推导是公开的,所以把B当作一个公平的游戏。据我所知,没有未定义的行为。

    所以,我呼吁大家共同的智慧。这是对批评的邀请。大家来试试吧。

    编辑:代码段的最后一行可以通过多种方式扩展成不那么令人讨厌的代码。例如:

    void Copy(A& to, const A& from)
    {
        to = from;
    }
    
    B x, y;
    Copy(x, y);
    

    功能相同。或者像这样:

    x.A::operator=(y);
    

    EDIT2:除了我,没有维修程序员。它来自一个爱好项目。所以别再可怜那个可怜的人了。

    9 回复  |  直到 13 年前
        1
  •  6
  •   px.    16 年前

    这一切都取决于上下文,也取决于哪个部分在这里被认为是“肮脏的”。

    首先,“切片复制”的伎俩在技术上是合法的。从形式上讲,这并不是真正的“黑客”。通过使用赋值运算符的限定名引用中的运算符,也可以获得相同的结果 A

    x.A::operator =(y); // same as `(A&) x = y` in your case
    

    它开始看起来很熟悉了,不是吗?是的,如果您必须在派生类中实现赋值运算符,如果您突然决定手动执行它,那么您会这样做

    B& B::operator =(const B& rhs) 
    {
      A::operator =(rhs); // or `this->A::operator =(rhs)`
    
      // B-specific part goes here
    }
    

    这个 A::operator =(rhs); 部分是完全相同的“切片复制”技巧,你上面,但在这种情况下,它是在不同的上下文中使用。当然,没有人会因为后一种用法而责怪你,因为这是通常的做法,也是应该这样做的。所以,同样,技巧的具体应用的“肮脏程度”取决于上下文。作为派生赋值运算符实现的一个组成部分,它是非常好的,但是在您的案例中,当它“单独”使用时,它可能看起来非常可疑。

    然而,第二点也是更重要的一点,在你的例子中,我所说的“肮脏”并不是使用“切片复制”本身的技巧。根据您在问题中描述的内容,看起来您实际上将数据结构拆分为两个类( A 和 B ) 明确地 为了能够使用上述技巧。 这个 在这种情况下我会称之为“肮脏”。不是“切片复制”的把戏本身,而是使用继承来实现“切片复制”的唯一目的。如果您这样做只是为了避免手动编写赋值运算符,那就是一个明显懒惰的例子。这就是这里的“肮脏”。我不建议将继承用于这种纯粹的功利目的。

        2
  •  4
  •   Terry Mahaffey    16 年前

    是的;这是肮脏的-您故意进行切片,因为您太懒了,无法编写B::CopyVariablesThatIWant(const B&)。您正在以一种适合您的方式滥用类型系统,但是您很可能会混淆和/或激怒任何未来的程序员,他们必须查看您的代码并找出它的意图。

    你的同事是对的,你应该为自己感到羞耻。

        3
  •  3
  •   dynamic    16 年前

    为什么不直接执行成员结构并显式执行操作:

    struct A
    {
        int a, b, c;
    };
    
    struct B
    {
        A top_;
        int d, e, f;
    };
    
    //And copy:
    
    B x, y;
    x.top_ = y.top_;
    

    在我看来,最肮脏的部分是 不必要的混淆 代码的一部分。六个月后,一些可怜的灵魂会诅咒你,试图理解为什么蝙蝠侠会这样做。

        4
  •  2
  •   UncleBens    16 年前

    问题:我有一个相当大的结构 用POD变量,我需要复制 在一些领域,但不是其他领域。 懒得写一篇文章 按成员复制功能。

    那就不要了(但任务会分配一切。)

    另外,如果你想切片,你可以做:

    B b;
    A a;
    a = b;
    

    仅仅拥有未初始化的成员看起来确实很肮脏。

        5
  •  2
  •   Peter Burns    16 年前

    是什么语义使某些字段成为副本所需,而不是其他字段?

    语义学 operator=() 之后这两个物体将有相等的可观察状态。(即 a = b; , a == b 为什么要违反这些语义并混淆维护程序员才是真正的问题。你认为不明确地写你的报告有什么长期的好处 MinimalClone() 功能,而不是长期的损害,以便于理解您的代码?

    编辑:总是有一个维护程序员,除非你在编译之后删除代码。我数不清有多少次我回到几个月前写的东西,然后说“我在想什么?!?”善待你的维护程序员,即使是你。

        6
  •  1
  •   Alex Brown    16 年前

    问题是您正在使用a构造函数初始化a B。我很惊讶编译器允许这样做。

    这意味着,当您访问B,并使用预期已初始化c、d、e的函数时,它将无法按预期工作,并可能崩溃。另外,如果它实际上是一个类并且具有虚拟函数,那么它的vtable将与编译器所期望的vtable不同。

    注意这个:

    #include <stdio.h>
    
    void f(A& y)
    {
      B x;
      x.d = 5;
      printf("%i\n",x.d);
    }
    
    void g(A& y)
    {
      B x;
      (A&)x = y;                                                  
      printf("%i\n",x.d);                                         
    }                                                             
    
    main()                                                        
    {                                                             
      B z;                                                        
      z.d = 3;                                                    
      f(z);                                                       
      g(z);                                                       
    } 
    

    编译并运行

    g++ 1.cc
    ./a.out
    5
    5
    

    请解释一下?

        7
  •  0
  •   Keith Randall    16 年前

    在下列情况下,您可能会遇到麻烦:

    struct A {
      char a;
      int b;
    };
    
    struct B : public A {
      char c;
    };
    

    a在结构中的偏移量0处分配,b在偏移量4处分配(填充以对齐b)。可以在偏移量1处分配c,因为A没有使用该空间。如果发生这种情况,您的分配可能会破坏c(如果A的复制构造函数复制了未使用的填充,则允许这样做)。

    我不确定C++中是否允许在一个填充空间中分配C。不过,我编写了一个Java编译器来实现这一点,所以这并不是史无前例的。

        8
  •  0
  •   Martin York    16 年前

    怎么样:

    struct A
    {
        int a;
        int b;
        int c;
    };
    struct B: public A
    {
        int d;
        int e;
        int f;
    };
    
    int main()
    {
        B   x;
        B   y;
        static_cast<A&>(x)  = y;
    
    }
    
        9
  •  0
  •   David Thornley    16 年前

    我反对的一个大问题是,它只为你节省了一点工作,并留下了一个很大的“嗯?”让下一个家伙过来。评论它并没有更好,因为它仍然留下一些额外的东西需要理解,而且更像是打字,而不仅仅是把它做好。

    经过几十年对各种各样的代码的研究,我真的开始讨厌任何阻碍阅读和理解的东西。