【问题标题】:Multiple inheritance via templates [closed]通过模板进行多重继承
【发布时间】:2015-05-08 11:24:24
【问题描述】:

用模板继承(线性)替换虚拟多重继承(菱形)是个好主意吗?例如我有这个类图:

       IBase
    /          \
   /            \
IExtendedBase  BaseImpl
  \            /
   ExtendedImpl

我知道我可以用虚拟继承来实现它。但是我可以使用模板来使这个图表线性化吗?

class IBase
    {
    public:
        virtual std::string name() = 0;
    };

    template<typename T>
    class BaseImpl : public T
    {
    public:
        virtual std::string name() override
        {
            return "BaseCommonImpl";
        }
    };

    template<typename T>
    class IExtendedBase : public T
    {
    public:
        virtual std::string extended_name() = 0;
    };

    template<typename T>
    class ExtendedBaseImpl : public T
    {
    public:
        virtual std::string extended_name() override
        {
            return "ExtendedBaseImpl";
        }
    };

现在有了 typedef,我可以专攻 ExtendedBase

typedef IExtendedBase<BaseImpl<IBase>> _ExtendedBase;
typedef ExtendedBaseImpl<_ExtendedBase> _ExtendedBaseImpl;

哪种方法更好?虚拟继承还是模板继承?

【问题讨论】:

  • Which method is better? 两者。两者都不。这取决于。
  • 一般来说,应避免使用继承方法来消除fragile base class problem,也请参阅“Holub on Patterns”一书,因为 Holub 对选择实现而不是扩展有一些相当强烈的意见,这使得很有意义。多重继承只会增加问题。
  • 在我看来,您的问题似乎不是关于 multiple 继承,而是关于一般的静态和动态继承(这是获得类似行为的完全不同的方法)。为了看到这一点,尝试设置一个基指针向量......它根本不适用于静态方法。因此,两种方法的组合通常是最方便的选择:将所有与派生类型无关的东西作为虚函数打包到基类中,然后从更高层次的静态继承开始。
  • 一个显着的区别:使用非模板版本,您可以根据需要使用IExtendedBase* 或IExtendedBase&amp; 或IBase* 或IBase&amp; 多态处理ExtendedImpl 实例,而您的模板意味着只想调度到extended_name() 的函数需要采用IExtendedBase&lt;BaseImpl&lt;IBase&gt;&gt;* 或&amp; 类型的参数,编码/限制BaseImpl 并使该函数对派生自IExtendedBase 但不是@987654337 的任何其他类无用@/IBase,除非函数本身是模板化的(通常具有优化和膨胀的潜力)。
  • 考虑改用type erasure。

标签: c++ templates inheritance multiple-inheritance diamond-problem


【解决方案1】:

虽然您可以使用这两种不同的方法(多重继承与模板)获得相似的结果,但存在重要的语义差异。

很遗憾,您没有提供足够的信息来推荐客观的选择。所以这里有一些注意事项:

多重继承方法

多重继承是为了强制执行有效的separation of concerns。

如果您的情况是 ExtendedImpl is-a IExtendBase 并同时 is-a,则应首选此方法 BaseImpl,但是两个继承关系是独立的。

在某些情况下(例如强制转换)会带来轻微的性能开销带来的不便。

但它的优点是允许您的ExtendedImpl 在任何可以使用其基础的地方使用。此外,它允许动态的、基于运行时的多态性(如果它的任何基础具有虚拟成员函数)。

模板方法

模板用于通用编程。如果您的ExtendedImpl 确实是通用的,并且“基础”是您的通用概念的更多参数,而不是进一步扩展的概念,则这种方法是首选。

这里的模板会采用性能稍好一些的方法(单继承)。但是您没有实现初始模式的原始概念。而且你没有动态多态的灵活性。

如果类型之间的关系不是通用的,您可能会在此处引入不希望的依赖关系。例如这里,IExtendedBase 将从 BaseImpl 继承。这在很多情况下都可以。但在其他情况下,这可能是完全不自然的,导致在维护阶段出现持久的设计问题。

结论

现在由您来决定哪种优势和不便最适合您的具体情况。如果需要,您可以编辑您的问题,更准确地说明上下文和您的意图,我会调整答案 因此。

【讨论】:

    【解决方案2】:

    这个问题是征求意见的,所以它可能会被版主关闭。

    在此之前,我的建议是支持避免多重继承的方法:

    #include <iostream>
    #include <string>
    
    class IBase
    {
    public:
        virtual std::string name() = 0;
    };
    
    
    class IExtendedBase : public IBase
    {
    public:
        virtual std::string extended_name() = 0;
    };
    
    
    template<typename T>
    class BaseImpl : public T
    {
    public:
        virtual std::string name() override
        {
            return "BaseCommonImpl";
        }
    };
    
    template<typename T>
    class ExtendedBaseImpl : public T
    {
        using inherited = T;
    public:
        virtual std::string extended_name() override
        {
            return "ExtendedBaseImpl";
        }
    
        // optionally override name if you wish
        std::string name() override {
            return inherited::name() + "(extended)";
        }
    };
    
    typedef BaseImpl<IBase> Base;
    typedef ExtendedBaseImpl<BaseImpl<IExtendedBase>> ExtendedBase;
    
    
    using namespace std;
    
    int main()
    {
    
        Base a;
        a.name();
        cout << a.name() << endl;
    
        ExtendedBase b;
        cout << b.extended_name() << endl;
        cout << b.name() << endl;
    
    }
    

    【讨论】:

      猜你喜欢
      • 2021-06-29
      • 2017-11-25
      • 1970-01-01
      • 1970-01-01
      • 2023-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-10
      相关资源
      最近更新 更多