里面对类含有 const 数据成员时,不能生成赋值操作符的说法很容易理解( 由于 const 数据项在初始化后无法修改,因此如果类包含 const 项,默认赋值运算符将不起作用。)
可是为什么含有“非静态类型引用数据成员”就不行呢?类自动生成的默认赋值操作符不是逐个成员进行赋值的吗。
我的理解:先定义一个含有引用数据成员 m_ref 的类,随后创建一个该类的对象 oa,再创建一个该类的对象 ob,然后把 oa 赋值给 ob。编译器生成的默认赋值操作符内是这么做的吧:ob.m_ref = oa.m_ref; ?
测试过程中发现了另一个问题(代码注释中说明了),附上代码:
#include <iostream>
class Foo
{
public:
Foo(int i = 11, int j = 22): m_i(i), m_ref(j)
{
std::cout << "In Foo constructor: " << std::endl;
std::cout << " m_i = " << m_i << std::endl;
std::cout << " m_ref = " << m_ref << std::endl; // 这里打印的值是由构造函数初始化列表中初始化的值。
}
// 如果编译器自动生成赋值操作符,里面是做下面的工作吗?
// 为啥不显式提供该赋值操作符就报 C4512 的警告呢?
Foo& operator=(const Foo& rhs)
{
if (this != &rhs)
{
this->m_i = rhs.m_i;
this->m_ref = rhs.m_ref;
}
return *this;
}
void f()
{
std::cout << "In Foo f(): " << std::endl;
std::cout << " m_i = " << m_i << std::endl;
std::cout << " m_ref = " << m_ref << std::endl; // 这里打印的值不是由构造函数初始化列表中初始化的值, 为什么?
}
private:
int m_i;
int& m_ref;
};
int main()
{
Foo of;
of.f();
std::cout << "---------------- 分隔 ----------------" << std::endl;
Foo of2(33, 44);
of2.f();
std::cout << "---------------- 分隔 ----------------" << std::endl;
of2 = of;
of2.f();
return 0;
}
执行结果:
In Foo constructor:
m_i = 11
m_ref = 22
In Foo f():
m_i = 11
m_ref = 1709282 // 为什么不是 22 ?
---------------- 分隔 ----------------
In Foo constructor:
m_i = 33
m_ref = 44
In Foo f():
m_i = 33
m_ref = 1709345 // 为什么不是 44 ?
---------------- 分隔 ----------------
In Foo f():
m_i = 11
m_ref = 1709408 // 为什么不是 22 ?
请 C++ 高手指点迷津,困惑好几天了!感谢。
22 个解决方案
#1
修改了下代码:
执行结果:
疑问:
从打印结果可以看到,f 方法中 m_ref 的地址和构造函数中的一致,代码中并无修改 m_ref 绑定的值。那它的值在什么地方被修改的?
另外,f 方法中打印的 m_ref 值不是它绑定数据的地址;编译一次,多次执行,m_ref 的地址不变,但 f 中打印的值每次都在变,是个随机值。这是为什么?
#include <iostream>
#include <iomanip>
class Foo
{
public:
Foo(int i = 11, int j = 22): m_i(i), m_ref(j)
{
std::cout << "In Foo constructor: " << std::endl;
std::cout << " m_i = " << m_i << std::endl;
std::cout << " &m_ref = " << &m_ref << std::endl;
std::cout << " dec(m_ref) = " << m_ref
<< std::hex << "\r\n hex(m_ref) = " << m_ref
<< std::dec << std::endl;
}
// 如果编译器自动生成赋值操作符,里面不是做下面的工作吗?
// 为啥不显示提供就报 C4512 的警告呢?
Foo& operator=(const Foo& rhs)
{
if (this != &rhs)
{
this->m_i = rhs.m_i;
this->m_ref = rhs.m_ref;
}
return *this;
}
void f()
{
std::cout << "In Foo f(): " << std::endl;
std::cout << " m_i = " << m_i << std::endl;
std::cout << " &m_ref = " << &m_ref << std::endl;
std::cout << " dec(m_ref) = " << m_ref
<< std::hex << "\r\n hex(m_ref) = " << m_ref
<< std::dec << std::endl;
}
private:
int m_i;
int& m_ref;
};
int main()
{
Foo of;
of.f();
std::cout << "---------------- 分隔 ----------------" << std::endl;
Foo of2(33, 44);
of2.f();
std::cout << "---------------- 分隔 ----------------" << std::endl;
of2 = of;
of2.f();
return 0;
}
执行结果:
In Foo constructor:
m_i = 11
&m_ref = 001CF984
dec(m_ref) = 22
hex(m_ref) = 16
In Foo f():
m_i = 11
&m_ref = 001CF984
dec(m_ref) = 16979218
hex(m_ref) = 1031512
---------------- 分隔 ----------------
In Foo constructor:
m_i = 33
&m_ref = 001CF984
dec(m_ref) = 44
hex(m_ref) = 2c
In Foo f():
m_i = 33
&m_ref = 001CF984
dec(m_ref) = 16979281
hex(m_ref) = 1031551
---------------- 分隔 ----------------
In Foo f():
m_i = 11
&m_ref = 001CF984
dec(m_ref) = 16979344
hex(m_ref) = 1031590
疑问:
从打印结果可以看到,f 方法中 m_ref 的地址和构造函数中的一致,代码中并无修改 m_ref 绑定的值。那它的值在什么地方被修改的?
另外,f 方法中打印的 m_ref 值不是它绑定数据的地址;编译一次,多次执行,m_ref 的地址不变,但 f 中打印的值每次都在变,是个随机值。这是为什么?
#2
因为你返回的是局部变量的引用,
In Foo constructor:
m_i = 11
m_ref = 22 //此时局部变量还存在
In Foo f()://局部变量已释放,变成undefined behavior
m_i = 11
m_ref = 1709282 // 为什么不是 22 ?
这样写看上去很不好: int& m_ref;
In Foo constructor:
m_i = 11
m_ref = 22 //此时局部变量还存在
In Foo f()://局部变量已释放,变成undefined behavior
m_i = 11
m_ref = 1709282 // 为什么不是 22 ?
这样写看上去很不好: int& m_ref;
#3
int& m_ref;
问题就是在这里
问题就是在这里
#4
不懂,关注一下
#5
引用必须初始化,而且不能改动
建议lz去看看,引用和指针(more effective C++)
建议lz去看看,引用和指针(more effective C++)
#6
哦,对!Foo(int i = 11, int j = 22): m_i(i), m_ref(j) 中的 j 按值传递,创建了个作用域仅限于构造函数的临时变量,我把让引用绑定到它上面了。
但,含有引用类型数据成员的类不显式声明赋值操作符为什么会有警告呢?
#7
这是规定啊。。。
你较什么真呢~死记吧
你较什么真呢~死记吧
#8
不能改动指不能改动它绑定的对象(指初始化它的对象),我的理解,它类似于 const指针(不是指向const对象的指针) 地址不能改一样。但像下面的代码是对的呀:
int a = 1;
int& ra = a;
int b = 2;
int& rb = b;
rb = ra; // 这里没有改动 rb,只是改动了 rb 指向的对象,也就是 a 的值。该句前后它的地址没有变。
另外,你不是这么写引用的吗: int& rx; ?
明天还得早起,太晚了,希望明天下班时问题能解决。
#9
你那里是赋值啊,为什么扯到地址的指向上去了?
#10
不较真,只是想搞清楚为什么?规定肯定有原因啊。
我修改了代码,仅突出显示 “为啥含有引用数据成员的类不能自动生成默认赋值操作符”的问题:
#include <iostream>
class Foo
{
public:
Foo(int i, int& j): m_i(i), m_ref(j) // 第 2 个参数改为引用类型了,这样就没有返回局部变量引用的问题,构造函数和下面的 f 方法中类成员数据打印结果是相同的了。
{
std::cout << "In Foo constructor: " << std::endl;
std::cout << " m_i = " << m_i << std::endl;
std::cout << " m_ref = " << m_ref << std::endl;
}
// 如果编译器自动生成赋值操作符,里面不是做下面的工作吗?
// 为啥不显示提供就报 C4512 的警告呢?
// 如果不显式提供下面的赋值操作符,main 中的 of2 = of; 就无法执行啊!
Foo& operator=(const Foo& rhs)
{
if (this != &rhs)
{
this->m_i = rhs.m_i;
this->m_ref = rhs.m_ref;
}
return *this;
}
void f()
{
std::cout << "In Foo f(): " << std::endl;
std::cout << " m_i = " << m_i << std::endl;
std::cout << " m_ref = " << m_ref << std::endl;
}
private:
int m_i;
int& m_ref;
};
int main()
{
int j1 = 22;
Foo of(11, j1);
of.f();
std::cout << "---------------- 分隔 ----------------" << std::endl;
int j2 = 44;
Foo of2(33, j2);
of2.f();
std::cout << "---------------- 分隔 ----------------" << std::endl;
of2 = of;
of2.f();
return 0;
}
打印结果:
In Foo constructor:
m_i = 11
m_ref = 22
In Foo f():
m_i = 11
m_ref = 22
---------------- 分隔 ----------------
In Foo constructor:
m_i = 33
m_ref = 44
In Foo f():
m_i = 33
m_ref = 44
---------------- 分隔 ----------------
In Foo f():
m_i = 11
m_ref = 22
#11
我现在的水平还只处于死记状态,等我毕业之后再来回答你吧,祝你好运。
我睡觉了
我睡觉了
#12
默认生成的那几个函数其实没那么简单,我是最近看EFFECTIVE C++ 看到的。
呵呵,我水平有限,拜拜了~
呵呵,我水平有限,拜拜了~
#13
顶一下,睡觉,困死鸟!
#14
不能改动是指不能改变绑定对象,它本身的值是可以变的,但同时它绑定的对象的值也变了
rb = ra; // 这里没有改动 rb,只是改动了 rb 指向的对象,也就是 a 的值。该句前后它的地址没有变。
你错了,rb还是指向b,而且是不会变的,只是rb和b的值都变成a的值了
之前说过了,推荐你去看一下 引用和指针
rb = ra; // 这里没有改动 rb,只是改动了 rb 指向的对象,也就是 a 的值。该句前后它的地址没有变。
你错了,rb还是指向b,而且是不会变的,只是rb和b的值都变成a的值了
之前说过了,推荐你去看一下 引用和指针
#15
<More Effective C++>, 感谢!
#16
顶一下,问题还没解决。
为啥含有引用数据成员的类不能自动生成默认赋值操作符
为啥含有引用数据成员的类不能自动生成默认赋值操作符
#17
还在问这个问题,真是有点一根筋
都跟你说了引用不能转变赋值,你叫编译器怎么帮你生成默认赋值操作符?
都跟你说了引用不能转变赋值,你叫编译器怎么帮你生成默认赋值操作符?
#18
“引用不能转变赋值”,为什么不可以改变赋值?只是值改变而已呀。
我对编译的知识不怎么了解,一直理解默认赋值操作符做的就是下面这样的事情。而且下面这么显示定义也没错。如果不是这样,它是怎么做的?
麻烦你给再解释下。
Foo& operator=(const Foo& rhs)
{
if (this != &rhs)
{
this->m_i = rhs.m_i;
this->m_ref = rhs.m_ref;
}
return *this;
}
#19
mark,关注下。
#20
跟你说过你的code的问题在于int& m_ref; 对这种诡异的code编译器不光是拒绝生成默认赋值操作符,默认的ctor也不会给你的。 至于为什么,可能是编译器觉得让他给不知道指向的引用赋值是很不安全的。
#21
是不是由于编译器提供的默认赋值函数始终是浅拷贝?
理由如下:
1. 发现有引用成员的类依然有默认的拷贝构造函数生成,即允许如下代码
2.确实不允许
3.他们之间有些什么区别呢?如果问题“是不是由于编译器提供的默认赋值函数始终是浅拷贝?”的回答是肯定的话,那么b=a这样的代码势必不会被通过,它导致一块内存丢失了。因而一个存在引用成员的类编译器不会生成默认的赋值构造函数。
------------------------------------------------------------------
请注意:以上是一些自己的看法。前提是建立在对问题“是不是由于编译器提供的默认赋值函数始终是浅拷贝?”的肯定回答的基础之上的。
一下代码片段呈现发生浅拷贝的情况
class X
{
public:
int& i;
explicit X(int& a):i(a){}
};
理由如下:
1. 发现有引用成员的类依然有默认的拷贝构造函数生成,即允许如下代码
int t = 7;
X a(t);
X b = a; //正常
2.确实不允许
int t = 7;
int v = 9;
X a(t);
X b(v);
b = a; //=操作不存在,错误
3.他们之间有些什么区别呢?如果问题“是不是由于编译器提供的默认赋值函数始终是浅拷贝?”的回答是肯定的话,那么b=a这样的代码势必不会被通过,它导致一块内存丢失了。因而一个存在引用成员的类编译器不会生成默认的赋值构造函数。
------------------------------------------------------------------
请注意:以上是一些自己的看法。前提是建立在对问题“是不是由于编译器提供的默认赋值函数始终是浅拷贝?”的肯定回答的基础之上的。
一下代码片段呈现发生浅拷贝的情况
int t = 7;
int v = 9;
X a(t);
X b(v);
//内存复制(浅拷贝),b.i原先指向的v丢失了
//(当然我们这里还有一个v的变量,如果b(v)之后把v出了作用于,我们就找不到了)
memcpy(&b,&a,sizeof(X));
cout<<b.i;
cout<<"-------------\n";
b.i = 18; //更改b.i的值
cout<<a.i<<'\t'<<b.i; //发现a.i也被更改了,a.i与b.i引用了同一个对象
#22
代码1不是默认拷贝函数,是转换构造函数。
#1
修改了下代码:
执行结果:
疑问:
从打印结果可以看到,f 方法中 m_ref 的地址和构造函数中的一致,代码中并无修改 m_ref 绑定的值。那它的值在什么地方被修改的?
另外,f 方法中打印的 m_ref 值不是它绑定数据的地址;编译一次,多次执行,m_ref 的地址不变,但 f 中打印的值每次都在变,是个随机值。这是为什么?
#include <iostream>
#include <iomanip>
class Foo
{
public:
Foo(int i = 11, int j = 22): m_i(i), m_ref(j)
{
std::cout << "In Foo constructor: " << std::endl;
std::cout << " m_i = " << m_i << std::endl;
std::cout << " &m_ref = " << &m_ref << std::endl;
std::cout << " dec(m_ref) = " << m_ref
<< std::hex << "\r\n hex(m_ref) = " << m_ref
<< std::dec << std::endl;
}
// 如果编译器自动生成赋值操作符,里面不是做下面的工作吗?
// 为啥不显示提供就报 C4512 的警告呢?
Foo& operator=(const Foo& rhs)
{
if (this != &rhs)
{
this->m_i = rhs.m_i;
this->m_ref = rhs.m_ref;
}
return *this;
}
void f()
{
std::cout << "In Foo f(): " << std::endl;
std::cout << " m_i = " << m_i << std::endl;
std::cout << " &m_ref = " << &m_ref << std::endl;
std::cout << " dec(m_ref) = " << m_ref
<< std::hex << "\r\n hex(m_ref) = " << m_ref
<< std::dec << std::endl;
}
private:
int m_i;
int& m_ref;
};
int main()
{
Foo of;
of.f();
std::cout << "---------------- 分隔 ----------------" << std::endl;
Foo of2(33, 44);
of2.f();
std::cout << "---------------- 分隔 ----------------" << std::endl;
of2 = of;
of2.f();
return 0;
}
执行结果:
In Foo constructor:
m_i = 11
&m_ref = 001CF984
dec(m_ref) = 22
hex(m_ref) = 16
In Foo f():
m_i = 11
&m_ref = 001CF984
dec(m_ref) = 16979218
hex(m_ref) = 1031512
---------------- 分隔 ----------------
In Foo constructor:
m_i = 33
&m_ref = 001CF984
dec(m_ref) = 44
hex(m_ref) = 2c
In Foo f():
m_i = 33
&m_ref = 001CF984
dec(m_ref) = 16979281
hex(m_ref) = 1031551
---------------- 分隔 ----------------
In Foo f():
m_i = 11
&m_ref = 001CF984
dec(m_ref) = 16979344
hex(m_ref) = 1031590
疑问:
从打印结果可以看到,f 方法中 m_ref 的地址和构造函数中的一致,代码中并无修改 m_ref 绑定的值。那它的值在什么地方被修改的?
另外,f 方法中打印的 m_ref 值不是它绑定数据的地址;编译一次,多次执行,m_ref 的地址不变,但 f 中打印的值每次都在变,是个随机值。这是为什么?
#2
因为你返回的是局部变量的引用,
In Foo constructor:
m_i = 11
m_ref = 22 //此时局部变量还存在
In Foo f()://局部变量已释放,变成undefined behavior
m_i = 11
m_ref = 1709282 // 为什么不是 22 ?
这样写看上去很不好: int& m_ref;
In Foo constructor:
m_i = 11
m_ref = 22 //此时局部变量还存在
In Foo f()://局部变量已释放,变成undefined behavior
m_i = 11
m_ref = 1709282 // 为什么不是 22 ?
这样写看上去很不好: int& m_ref;
#3
int& m_ref;
问题就是在这里
问题就是在这里
#4
不懂,关注一下
#5
引用必须初始化,而且不能改动
建议lz去看看,引用和指针(more effective C++)
建议lz去看看,引用和指针(more effective C++)
#6
哦,对!Foo(int i = 11, int j = 22): m_i(i), m_ref(j) 中的 j 按值传递,创建了个作用域仅限于构造函数的临时变量,我把让引用绑定到它上面了。
但,含有引用类型数据成员的类不显式声明赋值操作符为什么会有警告呢?
#7
这是规定啊。。。
你较什么真呢~死记吧
你较什么真呢~死记吧
#8
不能改动指不能改动它绑定的对象(指初始化它的对象),我的理解,它类似于 const指针(不是指向const对象的指针) 地址不能改一样。但像下面的代码是对的呀:
int a = 1;
int& ra = a;
int b = 2;
int& rb = b;
rb = ra; // 这里没有改动 rb,只是改动了 rb 指向的对象,也就是 a 的值。该句前后它的地址没有变。
另外,你不是这么写引用的吗: int& rx; ?
明天还得早起,太晚了,希望明天下班时问题能解决。
#9
你那里是赋值啊,为什么扯到地址的指向上去了?
#10
不较真,只是想搞清楚为什么?规定肯定有原因啊。
我修改了代码,仅突出显示 “为啥含有引用数据成员的类不能自动生成默认赋值操作符”的问题:
#include <iostream>
class Foo
{
public:
Foo(int i, int& j): m_i(i), m_ref(j) // 第 2 个参数改为引用类型了,这样就没有返回局部变量引用的问题,构造函数和下面的 f 方法中类成员数据打印结果是相同的了。
{
std::cout << "In Foo constructor: " << std::endl;
std::cout << " m_i = " << m_i << std::endl;
std::cout << " m_ref = " << m_ref << std::endl;
}
// 如果编译器自动生成赋值操作符,里面不是做下面的工作吗?
// 为啥不显示提供就报 C4512 的警告呢?
// 如果不显式提供下面的赋值操作符,main 中的 of2 = of; 就无法执行啊!
Foo& operator=(const Foo& rhs)
{
if (this != &rhs)
{
this->m_i = rhs.m_i;
this->m_ref = rhs.m_ref;
}
return *this;
}
void f()
{
std::cout << "In Foo f(): " << std::endl;
std::cout << " m_i = " << m_i << std::endl;
std::cout << " m_ref = " << m_ref << std::endl;
}
private:
int m_i;
int& m_ref;
};
int main()
{
int j1 = 22;
Foo of(11, j1);
of.f();
std::cout << "---------------- 分隔 ----------------" << std::endl;
int j2 = 44;
Foo of2(33, j2);
of2.f();
std::cout << "---------------- 分隔 ----------------" << std::endl;
of2 = of;
of2.f();
return 0;
}
打印结果:
In Foo constructor:
m_i = 11
m_ref = 22
In Foo f():
m_i = 11
m_ref = 22
---------------- 分隔 ----------------
In Foo constructor:
m_i = 33
m_ref = 44
In Foo f():
m_i = 33
m_ref = 44
---------------- 分隔 ----------------
In Foo f():
m_i = 11
m_ref = 22
#11
我现在的水平还只处于死记状态,等我毕业之后再来回答你吧,祝你好运。
我睡觉了
我睡觉了
#12
默认生成的那几个函数其实没那么简单,我是最近看EFFECTIVE C++ 看到的。
呵呵,我水平有限,拜拜了~
呵呵,我水平有限,拜拜了~
#13
顶一下,睡觉,困死鸟!
#14
不能改动是指不能改变绑定对象,它本身的值是可以变的,但同时它绑定的对象的值也变了
rb = ra; // 这里没有改动 rb,只是改动了 rb 指向的对象,也就是 a 的值。该句前后它的地址没有变。
你错了,rb还是指向b,而且是不会变的,只是rb和b的值都变成a的值了
之前说过了,推荐你去看一下 引用和指针
rb = ra; // 这里没有改动 rb,只是改动了 rb 指向的对象,也就是 a 的值。该句前后它的地址没有变。
你错了,rb还是指向b,而且是不会变的,只是rb和b的值都变成a的值了
之前说过了,推荐你去看一下 引用和指针
#15
<More Effective C++>, 感谢!
#16
顶一下,问题还没解决。
为啥含有引用数据成员的类不能自动生成默认赋值操作符
为啥含有引用数据成员的类不能自动生成默认赋值操作符
#17
还在问这个问题,真是有点一根筋
都跟你说了引用不能转变赋值,你叫编译器怎么帮你生成默认赋值操作符?
都跟你说了引用不能转变赋值,你叫编译器怎么帮你生成默认赋值操作符?
#18
“引用不能转变赋值”,为什么不可以改变赋值?只是值改变而已呀。
我对编译的知识不怎么了解,一直理解默认赋值操作符做的就是下面这样的事情。而且下面这么显示定义也没错。如果不是这样,它是怎么做的?
麻烦你给再解释下。
Foo& operator=(const Foo& rhs)
{
if (this != &rhs)
{
this->m_i = rhs.m_i;
this->m_ref = rhs.m_ref;
}
return *this;
}
#19
mark,关注下。
#20
跟你说过你的code的问题在于int& m_ref; 对这种诡异的code编译器不光是拒绝生成默认赋值操作符,默认的ctor也不会给你的。 至于为什么,可能是编译器觉得让他给不知道指向的引用赋值是很不安全的。
#21
是不是由于编译器提供的默认赋值函数始终是浅拷贝?
理由如下:
1. 发现有引用成员的类依然有默认的拷贝构造函数生成,即允许如下代码
2.确实不允许
3.他们之间有些什么区别呢?如果问题“是不是由于编译器提供的默认赋值函数始终是浅拷贝?”的回答是肯定的话,那么b=a这样的代码势必不会被通过,它导致一块内存丢失了。因而一个存在引用成员的类编译器不会生成默认的赋值构造函数。
------------------------------------------------------------------
请注意:以上是一些自己的看法。前提是建立在对问题“是不是由于编译器提供的默认赋值函数始终是浅拷贝?”的肯定回答的基础之上的。
一下代码片段呈现发生浅拷贝的情况
class X
{
public:
int& i;
explicit X(int& a):i(a){}
};
理由如下:
1. 发现有引用成员的类依然有默认的拷贝构造函数生成,即允许如下代码
int t = 7;
X a(t);
X b = a; //正常
2.确实不允许
int t = 7;
int v = 9;
X a(t);
X b(v);
b = a; //=操作不存在,错误
3.他们之间有些什么区别呢?如果问题“是不是由于编译器提供的默认赋值函数始终是浅拷贝?”的回答是肯定的话,那么b=a这样的代码势必不会被通过,它导致一块内存丢失了。因而一个存在引用成员的类编译器不会生成默认的赋值构造函数。
------------------------------------------------------------------
请注意:以上是一些自己的看法。前提是建立在对问题“是不是由于编译器提供的默认赋值函数始终是浅拷贝?”的肯定回答的基础之上的。
一下代码片段呈现发生浅拷贝的情况
int t = 7;
int v = 9;
X a(t);
X b(v);
//内存复制(浅拷贝),b.i原先指向的v丢失了
//(当然我们这里还有一个v的变量,如果b(v)之后把v出了作用于,我们就找不到了)
memcpy(&b,&a,sizeof(X));
cout<<b.i;
cout<<"-------------\n";
b.i = 18; //更改b.i的值
cout<<a.i<<'\t'<<b.i; //发现a.i也被更改了,a.i与b.i引用了同一个对象
#22
代码1不是默认拷贝函数,是转换构造函数。