C++ 对基本引用进行强制转换和复制是一种肮脏的黑客行为,但它到底有什么肮脏之处呢?
问题:我有一个包含POD变量的相当大的结构,我需要复制一些字段,但不能复制其他字段。懒得一个成员一个成员地写下复制函数 解决方案:将可复制字段移动到基础,分配基础。像这样: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 现在,这是肮脏的,我知道。我和同事们进
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;
}