2011-09-08 66 views
2

关于质疑“reference to abstract class”我写了下面的例子:参考抽象类分配问题

#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 

请问您指出我的错误是什么?

谢谢!

回答

4

data& d = db.get(0); 
std::cout << d.get_value() << std::endl; 
d = db.get(1); 
std::cout << d.get_value() << std::endl; 

的第一个语句是一个参考的初始化,而第三个语句是一个slicing assignment

您不能重新安排参考。 。

干杯&心连心,

+0

+1提切片。这是真正的问题。 –

2

原因是,引用不能被重新分配。

你赋予附图d的第一个元素在db

data& d = db.get(0); 

再后来你试图将其重新分配:

d = db.get(1); 

然而,这并不能改变引用本身,而不是它将参考点的值更改为

但是,在这种情况下,引用是不包含数据的抽象基类。所以这个任务并没有改变任何东西。

实际上,您打印第一个元素db两次,然后第二个元素再次两次。

0

以下行可能不是你所想: 数据& d = db.get方法(0); std :: cout < < d.get_value()< < std :: endl; d = db.get(1);

第三行d不会成为第二个数据库条目的引用。它仍然是第一个参考。参考只能在创建时初始化。

2

我要简化的例子了一下:

data_impl a(3), b(7); 
data &ra(a); 
data &rb(b); 
std::cout << ra.get_value() << std::endl; 
ra = rb;         // [1] 
std::cout << ra.get_value() << std::endl; 

现在与简化的代码更容易推理的程序。您可以获得参考raa子对象,但该参考的类型为data,而不是data_impl。与rbb类似。在标有[1]的行中,您正在执行从rbra的分配,表达式的两个参数的静态类型为data,这意味着无论它们引用的实际对象是什么,该特定行都是仅分配data子对象。这叫做切片

即,[1]被设置data子对象的a是相同data子对象的b。由于data不包含任何实际数据,因此结果是a保持不变。

对于更具说明性的示例,您可以将字段添加到data,并检查表达式是否修改a中的该字段,即使它未修改派生类中的字段。

然后,您可以尝试手动执行operator=作为虚拟函数,并检查您是否可以获得预期结果,但实现会有点混乱,因为C++中没有成员函数的协变参数,这意味着各级operator=的签名将采用(常量)参考data,并且您将被迫倒下(验证演员是否成功),然后执行分配...

0

您不能分隔参考从指标。 enter link description here

与指针不同,一旦引用被绑定到一个对象,它就不能被“重置”到另一个对象。引用本身不是一个对象(它没有身份;引用的地址给出了引用的地址;请记住:引用是它的引用)。

最奇怪的是编译器不警告它。

我尝试用gcc -Wall -pedantic

1

的问题可以在这里看到:

data& d = /**/; 

d = /**/;  <--- 

这里,静态类型的ddata&,这意味着它实际上是语法糖:

d.operator=(/**/); 

由于data::operator=不是虚拟的,因此它被调用。不派遣派生类。而且由于data是一个纯粹的接口,它没有任何属性,所以代码是没有意义的。


的问题是,这里没有真正的解决方案:

  • 制作operator=虚拟的是一个烂摊子,因为参数必须是在datadata const&,并且以后不能再在随后的派生类中改变,这意味着类型安全性的损失
  • 使用setValue/getValue虚拟对只有在派生类不包含比它通过此接口呈现的数据更多的数据时才有效,这是一个不合理的假设

由于问题无法解决,唯一的出路就是使诊断更容易:禁用基类中的复制和分配。

有两种方法可以做到这一点:

  • 使整个教主不可复制(无论是从boost::noncopyabledelete拷贝操作继承和拷贝赋值运算符,等...)

  • 使当前类不可复制,但通过复制构造函数和赋值运算符protected允许派生类自动复制。

一般情况下,它是一个非最终类有一个公共的拷贝构造函数和赋值运算符(我不知道任何的编译器诊断本)的错误。