【问题标题】:Make sure only Smartpointers are build确保只构建智能指针
【发布时间】:2016-03-23 21:57:39
【问题描述】:

我想确保只有智能指针是从我的类中构建的,所以我保护了所有构造函数。

为了创建对象,我构建了这个“可构建”-策略:

template <typename T>
class Buildable
{
public:
    template<typename ...Args>
    static QSharedPointer<T> buildObject(Args&&... all) 
{
        return QSharedPointer<T>( new T(std::forward<Args>(all)...) );
}

};

如果我在基类中使用此策略,一切顺利。 但是当我在基类和派生类中使用它时:

class A : public Buildable<A> {}
class B : A, public Buildable<B>{}

编译器认为:错误:在多个不同类型的基类中发现成员“buildObject”

我不知道如何解决这个问题。有什么想法吗?

【问题讨论】:

  • 如果你的构造函数是privateclass B : A, ..不能编译构造A。如果它受到保护,B 不再需要使用 smartPointer,因此 A(继承的)不在 smartPointer 中...
  • 有机会构建我想要的东西吗?强制使用智能指针?

标签: c++ qt c++11


【解决方案1】:

错误原因

这是因为这里的多重继承:

class B : A, public Buildable<B>{};

它会导致类 B 继承自 Buildable&lt;A&gt;Buildable&lt;B&gt;,两者都包含 buildObject() 重载。不幸的是,这两个重载仅在返回类型上有所不同。这是不允许的。

设计问题

不幸的是,您无法避免这种情况,除非您可以向buildObject() 走私一个附加参数,这可以允许编译器使用正确的类型派生来解决避免歧义。

但是你真的打算在你的设计中使用多重继承吗?如果你的类是多态的,你能不能使用如下定义的 B 类:

class B :  public A {};  // inherits from Buildable A 
...
QSharedPointer<A> b = B::buildObject();   //

另类设计

替代方法是将建筑类放在派生的底部,以避免冲突。保护 A 类和 B 类的构造函数,并使用此模板类:

// (I used here shared_ptr instead of QSharedPointer for testing purpose)    
template <typename T>
class Builder : public T
{
public:
    Builder() = delete; 
    Builder (const Builder&) = delete; 
    template<typename ...Args>
    static shared_ptr<T> buildObject(Args&&... all) 
    {
        return make_shared<T>(std::forward<Args>(all)...) ;
    }
};

class A { };    
class B : public A {};  // Normal class hierarchy.  
                        // can't be instantiated if ctor are protected.

shared_ptr<A> a = Builder<A>::buildObject(); 
shared_ptr<B> b = Builder<B>::buildObject(); 

【讨论】:

  • 为什么Builder必须继承自T?在 A 和 B 中,Builder 必须声明为友元,这样对吗?
  • @C.Seidel Builder 是一个 n 类,因此无需将其声明为朋友。 Builder 继承自 T 模板类型(即它将在​​类型推导 A 或 B 之后):这使其可以访问受保护的成员。这里是online demo
  • 您发布的演示适用于 std::shared_ptr。但是当用 QSharedPointer 替换时,编译器会抱怨私有构造函数。
  • 奇怪的是,对我来说,这不会编译(即使 QT 可用)ideone.com/Ls6UfD 错误:调用类“A”的受保护构造函数返回 QSharedPointer(new T(std:: forward(all)...) );
  • 通过继承获得访问权的想法很聪明,但我不会在代码中添加。继承传达了一种不存在的 Is-A 关系,即乍一看它是对读者的谎言。相比之下,每个相关类中的 friend 声明告诉代码的读者如何实例化它。
【解决方案2】:

设计问题的一个简单解决方案是使buildObject 成为一个独立的函数模板,即为每个用户类创建一个friend

为了简化friend 声明,您可能希望将该函数模板放在一个类中。

那么它很像你现有的代码,除了没有从工厂类继承:

#include <memory>       // std::shared_ptr
#include <utility>      // std::forward

namespace my{
    using std::forward;
    using std::shared_ptr;

    template< class Class >
    struct Make_
    {
        template< class... Args >
        static auto instance( Args&&... args )
            -> shared_ptr<Class>
        { return shared_ptr<Class>( new Class( forward<Args>( args )... ) ); }
    };

    class A
    {
    template< class > friend struct Make_;
    protected:
        A( int ) {}
    };

    class B
        : public A
    {
    template< class > friend struct Make_;
    protected:
        B( int x ): A( x ) {}
    };

}  // namespace my

auto main() -> int
{
    using namespace my;
    auto p = Make_<B>::instance( 42 );
}

提供协变函数而不重复其定义的直接技术问题的一般解决方案包括:

  • 宏(扩展为函数定义)。
    这是我推荐的,如果你觉得它绝对必须是一个成员函数。这是宏的少数合法用途之一。

  • 中间人继承。
    本质上不是直接从Base 和mixin 继承,而是从mixin 继承并要求它从Base 继承。您需要从 mixin 转发构造函数参数。

  • 在虚拟继承层次结构中占主导地位。
    一个非常丑陋和复杂的解决方案。不要去那里。但这是技术上的可能性。


最后,使用返回指向基类的(智能)指针的函数是无法解决的。客户端代码必须将其丢弃的地方。它以自己的方式确实很糟糕,但为了完整起见,我提到它。

【讨论】:

    【解决方案3】:

    您可以从QEnableSharedFromThis 继承(有关详细信息,请参阅here)。
    举个例子:

    class C: public QEnableSharedFromThis<C> {
        C() = default;
        // all the other constructors
    
    public:
        template<typename ...Args>
        static QSharedPointer<C> create(Args&&... all) {
            // refers one of the available constructors
            return QSharedPointer<C>(new C{std::forward<Args>(all)...});
        }
    
        QSharedPointer<C> getSharedFromThis() {
            return sharedFromThis();
        }
    };
    

    您可以将其用作层次结构的基类。

    【讨论】:

    • 但是我仍然需要在所有继承的类中编写几乎相同的创建方法。还是我错了?
    • 确实可以将其定义为模板类。这应该解决了定义 create 方法的问题。
    猜你喜欢
    • 2019-02-01
    • 1970-01-01
    • 2020-03-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-17
    • 2017-04-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多