如果 class 的成员是引用,为什么复制对象是非法的?
Why It is illegal to copy an object if a member of the class is a reference?
我遇到了一个小测验,说下面第 18 行的代码格式错误,因为 "It is ill-formed to use an implicitly defined assignment operator when one of the members that will need to be copied is a reference. "
我听不懂。为什么无法复制引用?为什么 16 号线是合法的?第 16 行和第 18 行很相似,复制构造函数仍然需要复制,对吧?
1 #include <iostream>
2
3 struct A
4 {
5 A(int& var) : r(var) {}
6
7 int &r;
8 };
9
10 int main(int argc, char** argv)
11 {
12 int x = 23;
13
14 A a1(x);
15
16 A a2 = a1;
17
18 a2 = a1;
19
20 return 0;
21 }
因为在 C++
中重新分配给引用是非法的。
int &a = some_int;
a = some_other_int; // value copied not reference
a = some_int; // value copied not reference
当您使用赋值运算符(由编译器生成)时,它会盲目地复制对象并因此尝试重新分配给您的引用,因此是无效的。
当你说 a2 = a1;
时,编译器会尝试将 a1.r
重新分配给 a2.r
,使其在编译时失败,因为它的格式错误。
您可以将引用视为 automatically dereferenced constant pointer
。所以行 a2 = a1;
将保持格式错误,原因与下面的 class 相同。
struct A
{
A(int *var) : p(var) {}
int * const p;
};
第16行使用拷贝构造函数,第18行使用赋值运算符operator=
。两种不同的功能,具有不同的限制。
因为无法重新绑定引用,所以编译器无法生成任何有意义的隐式赋值运算符。因此它拒绝这样做,并产生错误。
复制构造函数第一次生成对象,因此它可以像在您自己的构造函数中那样绑定该引用。
具有引用成员的 class 没有默认提供的 copy/move 赋值运算符。一旦建立绑定,就不能将引用重新绑定以引用不同的变量。简而言之,复制构造函数正在建立初始设置,而默认赋值运算符将在绑定后尝试更改。
因此,标准将这种情况排除在默认复制和移动赋值运算符之外。
C++11 § 12.8p23
A defaulted copy/move assignment operator for class X is defined as deleted if X has:
- 具有非平凡对应赋值运算符的变体成员,并且 X 是类联合 class,或
- const non-class 类型(或其数组)的非静态数据成员,或
- 引用类型的非静态数据成员,或
- class 类型 M(或其数组)的非静态数据成员不能是 copied/moved,因为应用于 M 的相应赋值运算符的重载决策 (13.3) 会导致歧义或从默认赋值运算符中删除或无法访问的函数,或
- 直接或虚拟基础 class B 不能 copied/moved 因为重载决策 (13.3) 应用于 B 的相应赋值运算符会导致歧义或函数被删除或无法从默认赋值运算符访问,或
- 对于移动赋值运算符,非静态数据成员或类型没有移动赋值运算符且不可简单复制的直接基class,或任何直接或间接虚拟基class.
您当然可以编写您自己的重载。
#include <iostream>
struct A
{
A(int& var) : r(var) {}
int &r;
A& operator=(const A& obj)
{
r = obj.r; // value copied, reference-binding remains the same
return *this;
}
};
int main(int argc, char** argv)
{
int x = 42;
int y = 43;
A a1(x);
A a2(y);
A a3 = a1; // legal. default copy-ctor invoked
a3 = a2; // legal. user-defined copy-assignment invoked
std::cout << x << ',' << y << '\n';
return 0;
}
输出
43,43
但这不会(也不能)重新绑定引用。此处提供的重载更改了 referenced 数据;不是参考资料本身。这样的区分很重要。
希望这对您有所帮助。
我遇到了一个小测验,说下面第 18 行的代码格式错误,因为 "It is ill-formed to use an implicitly defined assignment operator when one of the members that will need to be copied is a reference. "
我听不懂。为什么无法复制引用?为什么 16 号线是合法的?第 16 行和第 18 行很相似,复制构造函数仍然需要复制,对吧?
1 #include <iostream>
2
3 struct A
4 {
5 A(int& var) : r(var) {}
6
7 int &r;
8 };
9
10 int main(int argc, char** argv)
11 {
12 int x = 23;
13
14 A a1(x);
15
16 A a2 = a1;
17
18 a2 = a1;
19
20 return 0;
21 }
因为在 C++
中重新分配给引用是非法的。
int &a = some_int;
a = some_other_int; // value copied not reference
a = some_int; // value copied not reference
当您使用赋值运算符(由编译器生成)时,它会盲目地复制对象并因此尝试重新分配给您的引用,因此是无效的。
当你说 a2 = a1;
时,编译器会尝试将 a1.r
重新分配给 a2.r
,使其在编译时失败,因为它的格式错误。
您可以将引用视为 automatically dereferenced constant pointer
。所以行 a2 = a1;
将保持格式错误,原因与下面的 class 相同。
struct A
{
A(int *var) : p(var) {}
int * const p;
};
第16行使用拷贝构造函数,第18行使用赋值运算符operator=
。两种不同的功能,具有不同的限制。
因为无法重新绑定引用,所以编译器无法生成任何有意义的隐式赋值运算符。因此它拒绝这样做,并产生错误。
复制构造函数第一次生成对象,因此它可以像在您自己的构造函数中那样绑定该引用。
具有引用成员的 class 没有默认提供的 copy/move 赋值运算符。一旦建立绑定,就不能将引用重新绑定以引用不同的变量。简而言之,复制构造函数正在建立初始设置,而默认赋值运算符将在绑定后尝试更改。
因此,标准将这种情况排除在默认复制和移动赋值运算符之外。
C++11 § 12.8p23
A defaulted copy/move assignment operator for class X is defined as deleted if X has:
- 具有非平凡对应赋值运算符的变体成员,并且 X 是类联合 class,或
- const non-class 类型(或其数组)的非静态数据成员,或
- 引用类型的非静态数据成员,或
- class 类型 M(或其数组)的非静态数据成员不能是 copied/moved,因为应用于 M 的相应赋值运算符的重载决策 (13.3) 会导致歧义或从默认赋值运算符中删除或无法访问的函数,或
- 直接或虚拟基础 class B 不能 copied/moved 因为重载决策 (13.3) 应用于 B 的相应赋值运算符会导致歧义或函数被删除或无法从默认赋值运算符访问,或
- 对于移动赋值运算符,非静态数据成员或类型没有移动赋值运算符且不可简单复制的直接基class,或任何直接或间接虚拟基class.
您当然可以编写您自己的重载。
#include <iostream>
struct A
{
A(int& var) : r(var) {}
int &r;
A& operator=(const A& obj)
{
r = obj.r; // value copied, reference-binding remains the same
return *this;
}
};
int main(int argc, char** argv)
{
int x = 42;
int y = 43;
A a1(x);
A a2(y);
A a3 = a1; // legal. default copy-ctor invoked
a3 = a2; // legal. user-defined copy-assignment invoked
std::cout << x << ',' << y << '\n';
return 0;
}
输出
43,43
但这不会(也不能)重新绑定引用。此处提供的重载更改了 referenced 数据;不是参考资料本身。这样的区分很重要。
希望这对您有所帮助。