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

C++ 对基本引用进行强制转换和复制是一种肮脏的黑客行为,但它到底有什么肮脏之处呢?,c++,coding-style,C++,Coding Style,问题:我有一个包含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 现在,这是肮脏的,我知道。我和同事们进

问题:我有一个包含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视为一个公平的游戏。在我看来,没有未定义的行为

所以,我要向So的集体智慧发出呼吁。这是对批评的邀请。大家试试看

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

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

B x, y;
Copy(x, y);
功能相同。或者像这样:

x.A::operator=(y);
EDIT2:除了我,没有维护程序员。它来自一个爱好项目。所以别再可怜那个可怜的灵魂了

问题:我有一个相当大的结构 用POD变量,我需要复制 围绕一些领域,但不是其他领域。 懒得写一封信 成员对成员的复制功能

那就不要。(但分配将分配所有内容。)

此外,如果需要切片,可以执行以下操作:

B b;
A a;
a = b;

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

问题是您正在使用构造函数初始化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);                                                       
} 

请解释一下?

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


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

为什么不做一个成员结构并明确地做一些事情:

struct A
{
    int a, b, c;
};

struct B
{
    A top_;
    int d, e, f;
};

//And copy:

B x, y;
x.top_ = y.top_;

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

在以下情况下,你可能会遇到麻烦:

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编译器来实现这一点,所以这并非史无前例。

是什么语义使得副本需要一些字段,而不是其他字段

operator=()
的语义是,之后两个对象将具有等效的可观察状态。(也就是说,在
a=b;
之后,
a==b
应该返回true。)为什么要违反这些语义并混淆维护程序员才是真正的问题。您认为不显式编写
MinimalClone()
函数与易于理解代码的长期危害相比,有什么可能的长期好处


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

这一切都取决于上下文和这里的“脏”部分

首先,“切片复制”手法在技术上是合法的。从形式上讲,它不是真正的“黑客”。通过使用赋值运算符的限定名称从
A
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
),专门用于使用上述技巧。在这种情况下,这就是我所说的“肮脏”。不是“切片复制”技巧本身,而是为了启用“切片复制”而使用继承。如果您这样做只是为了避免手动编写赋值运算符,那么这就是明显的懒惰。这就是这里“脏”的地方。我不建议将继承用于这种纯粹的实用目的。

如何:

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;

}
结构A { int
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;

}