【问题标题】:Implementing interfaces in C++在 C++ 中实现接口
【发布时间】:2012-02-01 16:35:21
【问题描述】:

我通常使用 C# 进行编程,但我正在尝试做一些 C++ 并且在尝试用 C++ 实现接口时有些挣扎。

在 C# 中,我会这样做:

class Base<T>
{
    public void DoSomething(T value)
    {
        // Do something here
    }
}

interface IDoubleDoSomething
{
    void DoSomething(double value);
}

class Foo : Base<double>, IDoubleDoSomething
{
}

在 C++ 中我是这样实现的:

template <class T>
class Base
{
public:
    virtual void DoSomething(T value)
    {
        // Do something here
    }
};

class IDoubleDoSomething
{
public:
    virtual void DoSomething(double value) = 0;
};

class Foo : public Base<double>, public IDoubleDoSomething
{
};

问题是我无法实例化 Foo,因为它是抽象的(不实现 DoSomething)。我意识到我可以实现 DoSomething 并在 Base 上调用该方法,但我希望有更好的方法来做到这一点。我有其他类从具有不同数据类型的基类继承,还有其他类从不使用基类的 IDoubleDoSomething 继承。

任何帮助表示赞赏。

【问题讨论】:

  • 这有什么不好,还有什么地方可以“更好”?
  • 我想“更好”会像 C# 版本一样,其中 'Foo' 从 Base 继承方法并且不需要任何实现。我想从 Foo 中的 Base 实现每个方法并没有什么不好,只是感觉很乱。
  • 它很乱,但我从来没有找到更好的方法:(

标签: c# c++ interface multiple-inheritance porting


【解决方案1】:

考虑下面的 c++ 代码

class Foo
{
public:
    void DoSomething1(){}
};

template<typename t>
void MethodExpectsDosomething1( t f )
{
    f.DoSomething1();
}

template<typename t>
void MethodExpectsDosomething2( t f )
{
    f.DoSomething2();
}

int main()
{
    Foo f;
    MethodExpectsDosomething1<Foo>( f );

    MethodExpectsDosomething2<Foo>( f );

    return 0;
}

在 C++ 中,您可以使用 Foo 而无需实现 IDoSomething1IDoSomething2。第二种方法MethodExpectsDosomething2 将无法编译,因为Foo 没有DoSomething2 方法。

在 C# 中,这种构造是不可能的,它会强制您拥有 IDoSomething1IDoSomething2 接口并将其指定为 type constraint

所以也许你需要查看你的代码,看看是否需要这样的接口?

【讨论】:

  • duck-typing 的问题在于它不是自记录的,而涉及多态性的解决方案是自记录的。
  • 如果duck-typing 这么糟糕... C# 4.0 不会将它作为dynamic 关键字引入。仅仅因为你得到了一把斧头并不意味着你必须砍掉你的腿
  • 我可以提醒您,“它很流行,所以它不会有任何坏处”这个说法不是一个好的说法吗?如果您是模板化函数的用户,您如何确定该函数从您的对象请求哪种功能,以及使用哪种语义?动态多态不存在这个问题,因为接口就是文档。
  • @akappa:接口本身是一个糟糕的文档。当然,您会看到哪些成员签名,但这很少足以创建可与现有代码一起使用的具体实现。我认为我没有见过一个不包含不能作为签名一部分的假设的案例。概念是记录模板参数要求的一种方式(也是template &lt;typename Concept&gt; 约定)。
  • @akappa:(重点是,无论如何,您需要阅读外部文档或代码)。
【解决方案2】:

问题是,Base::DoSomethingIWhatever::DoSomething 是两个不相关的函数(即使其中没​​有任何纯虚函数,您也无法在 Foo 对象上调用 DoSomething,反正)。 Base 需要从 IWhatever 继承才能工作。

也就是说,问问自己是否真的需要这个。在 C++ 中,使用模板(和概念,有点像接口——参见 Boost.ConceptCheck)进行泛型编程比运行时子类型多态性更好。

【讨论】:

    【解决方案3】:

    您可以将第二个模板参数传递给 Base,它要实现的接口:

    template <typename T, class Interface>
    class Base : public Interface { ... };
    
    class Foo : public Base<double, IDoubleDoSomething> { ... };
    

    对于额外的奖励积分,您可以模板化 IDoubleDoSomething(例如 IDoSomething&lt;double&gt;),或使用特征类将类型 T 映射到相关接口。

    【讨论】:

      【解决方案4】:

      在 C++ 中,必须始终在派生类中重写纯虚函数;他们不能像这样从其他基类继承覆盖。如果您需要动态多态性,我认为除了在Foo 中编写一个调用Base 函数的函数之外,没有任何明智的选择。请注意,Base 函数不需要是虚拟的。

      根据您使用这些类的方式(特别是在编译时是否知道接口的每个实例的真实类型),您可以使用静态多态性将您的特定实现类注入到其用户中模板参数;例如:

      // No explicit interface specification with static polymorphism
      class Foo : public Base<double>
      {
          // Inherits DoSomething(double)
      };
      
      // Template can used with any class that impelements a 
      // "DoSomething" function that can accept a double.
      template <class DoubleDoSomething>
      void DoSomethingWithDouble(DoubleDoSomething & interface, double value)
      {
          interface.DoSomething(value);
      }
      
      // This particular specialisation uses a "Foo" object.
      Foo foo;
      DoSomethingWithDouble(foo, 42);
      

      【讨论】:

        【解决方案5】:

        正如其他人所指出的,基类中的两个函数是不相关的(尽管具有相同的名称和参数类型),因为它们没有共同的基类。如果你想让它们相关,你需要给它们一个通用的基类。

        另外,一般来说,如果你想让多重继承正常工作,你需要将你的非私有基类声明为virtual。否则,如果你曾经有通用的基类(这种代码风格经常需要这种基类),就会发生不好的事情。

        因此,您可以让您的示例按如下方式工作:

        template <class T>
        class IDoSomething {
        public:
            virtual void DoSomething(T value) = 0;
        };
        
        template <class T>
        class Base : public virtual IDoSomething<T>
        {
        public:
            virtual void DoSomething(T value)
            {
                // Do something here
            }
        };
        
        class IDoubleDoSomething : public virtual IDoSomething<double>
        {
        };
        
        class Foo : public virtual Base<double>, public virtual IDoubleDoSomething
        {
        };
        

        【讨论】:

        • 这似乎最接近我想要实现的目标,并且由于我拥有“基础”,因此它适用于我的场景。如果我不是“Base”的所有者,那么我想这里的其他解决方案会更适用。
        【解决方案6】:

        只要其他一切都井井有条,这应该可以工作。

        class Foo : public Base<double>
        {
        };
        

        在您的原始代码中,Foo 将有 2 个 void DoSomething(double value) 方法(1 个是抽象的)。 那将是一个 Base 部分和一个 IDoubleDoSomething 部分。 Foo::Base 和 Foo::IDoubleDoSomething。 您可以通过暂时提供和实现 IDoubleDoSomething.DoSomething() 来尝试它。

        但由于 Foo 已经“是一个”基础”,因此无需 IDoubleDoSomething 即可获得所需的一切。

        【讨论】:

        • 使用接口的原因是传递一个指向它的指针而不需要定义基类或派生类。消除接口类是倒退。
        猜你喜欢
        • 2012-03-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-04-30
        • 2010-11-11
        相关资源
        最近更新 更多