参考抽象类分配问题
关于问题“对抽象类的引用”我写了以下示例:
#include <iostream>
#include <vector>
class data
{
public:
virtual int get_value() = 0;
};
class data_impl : public data
{
public:
data_impl( int value_ ) : value( value_ ) {}
virtual int get_value() { return value; }
private:
int value;
};
class database
{
public:
data& get( int index ) { return *v[index]; }
void add( data* d ) { v.push_back( d ); }
private:
std::vector< data* > v;
};
int main()
{
data_impl d1( 3 );
data_impl d2( 7 );
database db;
db.add( &d1 );
db.add( &d2 );
data& d = db.get( 0 );
std::cout << d.get_value() << std::endl;
d = db.get( 1 );
std::cout << d.get_value() << std::endl;
data& d_ = db.get( 1 );
std::cout << d_.get_value() << std::endl;
d_ = db.get( 0 );
std::cout << d_.get_value() << std::endl;
return 0;
}
令我惊讶的是,示例打印:
3
3
7
7
看起来参考作业的工作与我的预期不同。我希望:
3
7
7
3
你能指出我的错误是什么吗?
谢谢你!
In relation to question "reference to abstract class" I wrote the following example:
#include <iostream>
#include <vector>
class data
{
public:
virtual int get_value() = 0;
};
class data_impl : public data
{
public:
data_impl( int value_ ) : value( value_ ) {}
virtual int get_value() { return value; }
private:
int value;
};
class database
{
public:
data& get( int index ) { return *v[index]; }
void add( data* d ) { v.push_back( d ); }
private:
std::vector< data* > v;
};
int main()
{
data_impl d1( 3 );
data_impl d2( 7 );
database db;
db.add( &d1 );
db.add( &d2 );
data& d = db.get( 0 );
std::cout << d.get_value() << std::endl;
d = db.get( 1 );
std::cout << d.get_value() << std::endl;
data& d_ = db.get( 1 );
std::cout << d_.get_value() << std::endl;
d_ = db.get( 0 );
std::cout << d_.get_value() << std::endl;
return 0;
}
To my surprise, the example prints:
3
3
7
7
and it looks like the reference assignment does the work differently from my expectation. I would expect:
3
7
7
3
Could you please point what my mistake is?
Thank you!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
第
一个语句是引用初始化,而第三个语句是切片赋值。
您无法重新设置参考。
干杯&呵呵,,
In
the first statement is a reference initialization, while the third statement is a slicing assignment.
You can not re-seat a reference.
Cheers & hth.,
原因是,引用不能重新分配。
您将引用
d
分配给db
中的第一个元素:然后您尝试重新分配它:
但是,这不会更改引用本身,而是更改参考点的值。
然而,在这种情况下,引用是对不包含数据的抽象基类的引用。所以任务并没有改变任何东西。
实际上,您将
db
中的第一个元素打印两次,然后再打印第二个元素两次。The reason is, references can't be reassigned.
You assigned the reference
d
to the first element indb
:Then later you tried to reassign it:
However, this doesn't change the reference itself, instead it changes the value the reference points to.
However, in this case the reference is to an abstract base class which contains no data. So the assignment isn't changing anything.
In effect you print the first element in
db
twice, then the 2nd element again twice.我将稍微简化一下这个示例:
现在有了简化的代码,就可以更容易地推理该程序。您获得了对
a
子对象的引用ra
,但该引用的类型为data
,而不是data_impl
。与rb
和b
类似。在标有 [1] 的行中,您正在执行从rb
到ra
的赋值,表达式的两个参数的静态类型是data
,这意味着无论它们引用的实际对象是什么,该特定行都仅分配data
子对象。这称为切片。也就是说,[1] 将
a
的data
子对象设置为与b
的data
子对象相同>。由于data
不包含任何实际数据,因此结果是a
保持不变。作为更具说明性的示例,您可以向
data
添加一个字段,并检查表达式是否修改了a
中的该字段,即使它没有修改导出中的字段类。然后你可以尝试手动实现
operator=
作为虚函数,并检查是否可以获得预期的结果,但实现会有点混乱,因为 C++ 中的成员函数没有协变参数,这意味着各级operator=
的签名将采用对data
的(const)引用,并且您将被迫进行向下转型(验证转型是否成功)然后执行任务...I am going to simplify the example a bit:
Now with that simplified code it is easier to reason about the program. You obtain a reference
ra
to thea
subobject, but the reference is of typedata
, rather thandata_impl
. Similarly withrb
andb
. In the line marked with [1] you are performing an assignment fromrb
tora
, the static type of the two arguments of the expression isdata
, and that means that regardless of what the real object they refer to is, that particular line is assigning only thedata
subobject. This is called slicing.That is, [1] is setting the
data
subobject ofa
to be the same as thedata
subobject ofb
. Becausedata
does not contain any actual data, the result is thata
remains unmodified.For a more illustrative example, you can add a field to
data
, and check that the expression does modify that field ina
, even if it does not modified the fields in derived classes.Then you can try an implement
operator=
manually as a virtual function and check that you can get the expected result, but the implementation will be a bit messier, as there are no covariant arguments to member functions in C++, which means that the signature ofoperator=
at all levels will take a (const) reference todata
, and you will be forced to downcast (verify that the cast succeeded) and then perform the assignment...问题可以在这里看到:
这里,
d
的静态类型是data&
,这意味着它实际上是语法糖:Since
data::operator =
不是virtual,那么就调用了。不分派到派生类。而且由于data
是一个纯接口,它没有任何属性,所以代码没有任何意义。问题是,这里没有真正的解决方案:
operator=
设为虚拟是一团糟,因为参数必须是data
data const& >,并且以后不能在后续的派生类中更改,这意味着类型安全性的损失setValue
/getValue
虚拟对仅在派生类不包含更多内容时才有效数据比他们通过这个接口呈现的数据要多,这是不合理的假设既然问题无法解决,唯一的出路就是让诊断变得更容易:在基类中禁用复制和赋值。
有两种方法可以做到这一点:
使整个层次结构不可复制(继承自
boost::noncopyable
或删除
复制运算符和复制赋值运算符,等等...)使当前类不可复制,但允许派生类自动复制,方法是将复制构造函数和赋值运算符设置为受保护。
一般来说,非最终类拥有公共复制构造函数和赋值运算符是一个错误(我想知道是否有编译器对此进行诊断)。
The problem can be seen here:
Here, the static type of
d
isdata&
, meaning that it is in fact syntactic sugar for:Since
data::operator=
is not virtual, then it is called. No dispatch to a derived class. And sincedata
is a pure interface, it does not have any attribute, so the code is meaningless.The problem is, there are no real solution here:
operator=
virtual is a mess, because the parameter must be adata const&
indata
, and cannot later be changed in subsequent derived classes, meaning a loss of type safetysetValue
/getValue
virtual pair will only work if the derived classes do not hold more data than they present via this interface, which is an unreasonnable assumptionSince the problem cannot be solved, the only way out is to make diagnosis easier: disabling copy and assignment in the base class.
There are two ways to do this:
Make the whole hierarch non-copyable (either inherit from
boost::noncopyable
ordelete
the copy operator and copy assignment operator, etc...)Make the current class non-copyable, but allow derived classes to be copyable automatically, by making the copy constructor and assignment operator
protected
.In general, it is an error for a non-final class to have a public copy constructor and assignment operator (I wonder if any compiler diagnose this).
以下几行可能不是您所想的:
数据& d = db.get( 0 );
std::cout << d.get_value() << std::endl;
d = db.get( 1 );
第三行,d 不会成为对第二个数据库条目的引用。仍然是对第一个的引用。引用只能在创建时初始化。
Following lines might not be what you think:
data& d = db.get( 0 );
std::cout << d.get_value() << std::endl;
d = db.get( 1 );
the third line, d does not become a reference to the second database entry. It is still a reference to the first. References can only be initialized on creation.
您无法将引用与所指对象分开。 在此处输入链接描述
与指针不同,一旦引用绑定到对象,它不能“重新安置”到另一个对象。引用本身不是一个对象(它没有标识;获取引用的地址即可得到所指对象的地址;记住:引用是其所指对象)。
最奇怪的是编译器不警告它。
我尝试使用
gcc -Wall -pedantic
You can't separate the reference from the referent. enter link description here
Unlike a pointer, once a reference is bound to an object, it can not be "reseated" to another object. The reference itself isn't an object (it has no identity; taking the address of a reference gives you the address of the referent; remember: the reference is its referent).
The strangest thing is the compiler don't warn it.
I try with
gcc -Wall -pedantic