C++如何初始化抽象基类引用元素?

Posted

技术标签:

【中文标题】C++如何初始化抽象基类引用元素?【英文标题】:C++ How to initilize abstract base class reference element? 【发布时间】: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,不适合实现类的属性吗? 您可以选择在默认构造函数中保持引用未初始化。这确实是一个奇怪的设计,但它会让编译器闭嘴。 如果构造函数未初始化引用,则会导致错误。 【参考方案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】:

莫恩,

首先,我认为您还必须为ImpOneImpTwo 声明一个构造函数。否则,他们会在构建时尝试调用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

【讨论】:

首先它相当复杂,其次只有在所有返回类型都是无效的情况下才有效。我承认这是我给出的例子,但它不适用于我的实际程序,对不起。

以上是关于C++如何初始化抽象基类引用元素?的主要内容,如果未能解决你的问题,请参考以下文章

C ++为所有派生类初始化抽象基类受保护成员

接受对抽象类的 const 引用的 C++ 构造函数无法初始化 std::map

关于C++基类与派生类

C++中如何在子类的构造函数中调用基类的构造函数来初始化基类成员变量

python:抽象基类'__init__():初始化还是验证? [关闭]

在其派生类C++的构造函数中调用基类的构造函数[重复]