【问题标题】:Pimpl idiom, separate interface/implementation files, and multiple virtual inheritance. How?Pimpl 习惯用法、单独的接口/实现文件和多重虚拟继承。如何?
【发布时间】:2017-06-30 20:48:25
【问题描述】:

以下头文件和实现文件结合了所有三个概念,但不会编译:

$ cat a.h
#include <memory>

class Base {
protected:
    class BaseImpl;
    std::shared_ptr<BaseImpl> pImpl;
    Base(BaseImpl* impl)
        : pImpl{impl}
    {}
public:
    virtual ~Base()
    {}
    virtual void func() =0;
};

class Der : public virtual Base {
private:
    class DerImpl;
    DerImpl* getPimpl() const noexcept;
public:
    Der();
    virtual ~Der()
    {}
    void func();
};
$ cat a.cpp
#include "a.h"

class Base::BaseImpl {
};

class Der::DerImpl : public virtual Base::BaseImpl {
public:
    void func() {}
};  

Der::Der()
    : Base{new DerImpl()}
{}  

Der::DerImpl* Der::getPimpl() const noexcept {
    return static_cast<DerImpl*>(pImpl.get());
}

void Der::func() {
    getPimpl()->func();
}
$ g++ --std=c++11 -c a.cpp
a.cpp: In member function ‘Der::DerImpl* Der::getPimpl() const’:
a.cpp:16:45: error: cannot convert from base ‘Base::BaseImpl’ to derived type ‘Der::DerImpl’ via virtual base ‘Base::BaseImpl’
     return static_cast<DerImpl*>(pImpl.get());
                                             ^

您能否告诉我有什么问题,为什么以及如何使用 Pimpl 习惯用法 多重虚拟继承拥有单独的声明和定义文件。

【问题讨论】:

标签: c++ c++11 multiple-inheritance


【解决方案1】:

多个虚拟基类问题和 pimpl 成语是完全不同的主题。 pimpl 和 impl 通常位于单独的头文件和 cpp 文件中。整个 pimpl 习惯用法是一个指向实现的指针,这意味着 pimple 包装了 impl 的函数以将它们公开给客户端,并且只包含一个指向 impl 的 void 指针,该指针被强制转换为 pimpl 的 cpp 文件中的实际 impl,并创建和销毁它在它的构造函数和解构函数中。如果您愿意,您还可以实现更多功能,但这种类型的惯用语通常用于防止知识产权在二进制 dll 附带的头文件中暴露。它还可以巩固您的界面,这样您就不会在产品的未来版本中意外破坏客户端应用程序。

它是用于库共享,而不是单个应用程序的生产代码。至于基类和继承,你的基类有一个指向它自身实例的指针。让我们一次专注于一件事。 This 应该有助于为您详细解释 pimpl 成语。如果您的产品需要多个虚拟继承,并且它是一个库,如果您愿意,只需使用 pimpl idiom 公开它。如果您需要复习多重继承,您可以找到here

【讨论】:

    【解决方案2】:

    编译器错误告诉你不能static_cast 从派生类型到基类型。使用dynamic_castreinterpret_castreinterpret_cast 更快,但不能保证在多重继承下正常工作)。

    附言另外,不要忘记虚拟基类的虚拟析构函数。

    【讨论】:

    • dynamic_cast 不起作用,reinterpret_cast 在预期的多重继承环境中不是一个选项(它导致了 SIGSEGV)。
    • @SteveEmmerson,RTTI 开启了吗?如果是,dynamic_cast 应该可以工作。
    • 我不知道。然而,这一点没有实际意义,因为我发现了一种比使用dynamic_cast 更有效的解决方法。
    【解决方案3】:

    我设法将这些概念(pImpl 与单独的文件和多个虚拟继承)结合起来

    • pImpl 中保存指向void 的指针而不是指向实现的指针,以避免基构造函数修改指针;和
    • 在派生类的getPimpl() 中使用reinterpret_cast 而不是static_cast 来解决在虚拟继承环境中无法向下转换的问题。

    这是代码和测试例程:

    $ cat a.h
    #include <memory>
    
    class Base {
    protected:
        class Impl;
        std::shared_ptr<void> pImpl;
        Base(void* impl) : pImpl{impl} {}
    public:
        virtual ~Base() =0; 
    };
    
    class Der1 : public virtual Base {
    protected:
        class Impl; // Won't compile if `private`
    private:
        Impl* getPimpl() const noexcept;
    public:
        Der1();
        virtual ~Der1() {}
        void der1Func();
    };
    
    class Der2 : public virtual Base {
    protected:
        class Impl; // Won't compile if `private`
    private:
        Impl* getPimpl() const noexcept;
    public:
        Der2();
        virtual ~Der2() {}
        void der2Func();
    };
    
    class Joined : public Der1, public Der2 {
    private:
        class Impl;
        Impl* getPimpl() const noexcept;
    public:
        Joined();
        void joinedFunc();
    };
    $ cat a.cpp
    #include "a.h"
    #include <iostream>
    
    class Base::Impl {};
    
    class Der1::Impl : public virtual Base::Impl {
    public:
        void der1Func() {
            std::cout << "Der1::Impl::der1Func() called\n";
        }   
    };
    
    class Der2::Impl : public virtual Base::Impl {
    public:
        void der2Func() {
            std::cout << "Der2::Impl::der2Func() called\n";
        }   
    };
    
    class Joined::Impl : public virtual Der1::Impl, public virtual Der2::Impl {
    public:
        void joinedFunc() {
            std::cout << "Joined::Impl::joinedFunc() called\n";
        }   
    };
    
    Base::~Base() {
        reinterpret_cast<Impl*>(pImpl.get())->~Impl();
    }
    
    Der1::Der1() : Base{new Impl()} {}
    
    Der1::Impl* Der1::getPimpl() const noexcept {
        return reinterpret_cast<Impl*>(pImpl.get());
    }
    
    void Der1::der1Func() {
        getPimpl()->der1Func();
    }
    
    Der2::Der2() : Base{new Impl()} {}
    
    Der2::Impl* Der2::getPimpl() const noexcept {
        return reinterpret_cast<Impl*>(pImpl.get());
    }
    
    void Der2::der2Func() {
        getPimpl()->der2Func();
    }
    
    Joined::Joined() : Base{new Impl()} {}
    
    Joined::Impl* Joined::getPimpl() const noexcept {
        return reinterpret_cast<Impl*>(pImpl.get());
    }
    
    void Joined::joinedFunc() {
        getPimpl()->joinedFunc();
    }
    
    int main() {
        Joined* joined = new Joined();
        joined->joinedFunc(); // Calls Der1::joinedFunc()
        joined->der1Func();   // Calls Der1::der1Func()
        joined->der2Func();   // Calls Der2::der2Func()
        Der1*   der1 = joined;
        der1->der1Func();     // Calls Der1::der1Func()
    }
    $ g++ --std=c++11 a.cpp && ./a.out
    Joined::Impl::joinedFunc() called
    Der1::Impl::der1Func() called
    Der2::Impl::der2Func() called
    Der1::Impl::der1Func() called
    $
    

    【讨论】:

      猜你喜欢
      • 2014-10-13
      • 1970-01-01
      • 2010-09-12
      • 1970-01-01
      • 2018-06-23
      • 2011-11-21
      • 2012-10-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多