【问题标题】:C++ How to initilize abstract base class reference element?C++如何初始化抽象基类引用元素?
【发布时间】:2014-04-06 13:35:27
【问题描述】:

我的问题是基于典型的菱形层次结构,但不是典型的菱形问题。

class Interface
{
public:
    int value; 
    // SomeBigData &data; 
    Interface(int _value = 0) : value(_value) {}; 

    virtual void function1() = 0; 
    virtual void function2() = 0; 
};

class ImpOne : public virtual Interface
{
public:
    void function1() { ++value; }
};

class ImpTwo : public virtual Interface
{
public:
    void function2() { --value; }
};

class Device : public ImpOne, public ImpTwo
{
public:
    Device() : Interface(7) {}; 
};

有一个抽象接口定义了许多功能(上面的例子是简化的)。然后有一些实现类一次只实现其中的几个功能。最后,还有一个非抽象类,如上面的 Device 类,通过将几个实现类组合在一起来实现整个接口。

现在,问题是这样的:

Interface 类中有一个整数元素,它由 Device 类初始化并在实现类之间共享,到目前为止一切都很好。但是如果我从Interface(int _value = 0) 构造函数中删除= 0,整个系统就会崩溃,因为我缺少接口类的默认构造函数,这有点奇怪,因为它永远不会被调用。

为什么这会困扰我?正如代码中所建议的,接口类需要包含对复杂数据结构(不属于该类)的引用,而不是在所有派生实现类之间共享。然而,为引用指定默认值既不明智也不可能。

所以问题是:

如何正确初始化接口类(引用)元素,例如代码中建议的SomeBigData &data,我无法为默认构造函数指定默认值(无论如何都不会调用)?还是我做错了什么?

【问题讨论】:

  • “整个系统崩溃”是什么意思?
  • 接口的概念(不是 c++ 概念)需要一个抽象类,它不包含(!)数据,而只包含虚函数。
  • 如果Interface类应该提供接口,为什么它有一个属性value,不适合实现类的属性吗?
  • 您可以选择在默认构造函数中保持引用未初始化。这确实是一个奇怪的设计,但它会让编译器闭嘴。
  • 如果构造函数未初始化引用,则会导致错误。

标签: c++ inheritance virtual-inheritance diamond-problem


【解决方案1】:

你可以这样做:

class Interface {
public:
    int value; 
    virtual SomeBigData& getBigData() = 0;
    Interface(int _value = 0) : value(_value) {}; 

    virtual void function1() = 0; 
    virtual void function2() = 0; 
};

class BigDataProvider : public virtual Interface {
public:
    SomeBigData& data_;
    BigDataProvider(SomeBigData& data) : data_(data) {}
    SomeBigData& getBigData() { return data_; }
};

class Device : public BigDataProvider, public ImpOne, public ImpTwo {
public:
    Device(SomeBigData& data) : BigDataProvider(data), Interface(7) {}
};

您的成员value 也可以使用相同的模式。那么Interface 将是一个“纯界面”,您可以避免整体上的菱形类布局。

【讨论】:

  • 这很好。我最初的解决方案是类似的,但由于广泛的 getter 定义和使用,我拒绝了它。
  • 我的意思是,如果这些函数之一在循环中调用其他函数,并且每个调用都需要从远处加载所有(比如说 20 个)元素/引用,然后再执行一些可能微不足道的事情,这是对计算时间(和代码行)的一种相当浪费。如果函数可以直接到达元素,那么使用起来会更容易和更快。正如我在示例中演示的那样,它可以完成(至少对于某些类型的元素)。我知道我正在尝试做的事情并不完全是纯粹的,但我这样做是因为它的有效性并节省了大约一百行代码。
【解决方案2】:

你可以声明一个没有实现的默认构造函数:(https://ideone.com/ZD336z)

class Interface
{
public:
    Interface(int _value, SomeBigData &data) : value(_value), data(data) {}
    ~Interface() {}

    virtual void function1() = 0;
    virtual void function2() = 0;
protected:
    Interface(); // No implementation
protected:
    int value;
    SomeBigData &data;
};

该方法应该对派生类可见,但不需要实现,因为只调用了另一个构造函数。

【讨论】:

  • 是的,这确实很好用。我之前尝试过 public 和 =delete ,但不知何故我似乎忘记了保护。非常感谢。
  • 其实不行。出于某种原因,它似乎已经编译(?困惑?),但编译器要求实现您提供的代码。
  • @user2828383: 哪个编译器?请注意,如果您从 Device 派生,则每个具体的子类都应调用 Interface(value, data)(因为它使用虚拟继承)。
  • @user2828383:刚刚在 MSVC 2013 上进行了测试,我刚刚得到了 warning C4250: 'Device' : inherits 'ImpOne::ImpOne::function1' via dominance。没有错误。
  • 好吧,如果我完全复制您所写的内容(来自 url),我会收到一个链接错误:错误 LNK2019: unresolved external symbol "protected: __thiscall Interface::Interface(void)" (?? 0Interface@@IAE@XZ) 在函数“public: __thiscall ImpOne::ImpOne(void)”中引用 (??0ImpOne@@QAE@XZ)
【解决方案3】:

莫恩,

首先,我认为您还必须为ImpOne 和ImpTwo 声明一个构造函数。否则,他们会在构建时尝试调用Interface(),而这并不存在。 其次,你可以使用Composite Pattern来避免钻石问题。

class Interface                         // abstract
{
protected:
    Base() {}

public:
    virtual void f1(int &value) {}
    virtual void f2(int &value) {}
}

class ImpOne: Base                    
{
public:
    void f1(int &value) { value++; }
}

class ImpTwo : Base                    
{
public:
    void f2(int &value) { value--; }
}

class DeviceBase                        // abstract base for all "Devices"
{
protected:
    int value;
    List<Interface*> Leaves;            // use std::vector or whatever dependent 
                                        // on your library
    Composite(int val) { value = val; }

public:
    void f1()
    {
        for (Interface* const leaf : Leaves)
            leaf->f1(value);
    }

    void f2()
    {
        for (Interface* const leaf : Leaves)
            leaf->f2(value);
    }
}

class Device : DeviceBase
{
public:
    Device(int val) : DeviceBase(val)
    {
        Leaves << new ImpOne(value);
        Leaves << new ImpTwo(value);
    }
}    

Greez Albjeno

【讨论】:

  • 首先它相当复杂,其次只有在所有返回类型都是无效的情况下才有效。我承认这是我给出的例子,但它不适用于我的实际程序,对不起。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-13
  • 2011-05-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-21
  • 2021-03-23
  • 1970-01-01
相关资源
最近更新 更多