代码之家  ›  专栏  ›  技术社区  ›  Aaron Fischer

如何在C++中声明接口?

  •  757
  • Aaron Fischer  · 技术社区  · 16 年前

    如何设置表示接口的类?这只是一个抽象的基类吗?

    15 回复  |  直到 7 年前
        1
  •  651
  •   Community dbr    7 年前

    扩展答案 bradtgmurray ,您可能希望通过添加虚拟析构函数对接口的纯虚拟方法列表进行一次异常。这允许您将指针所有权传递给另一方,而不公开具体的派生类。析构函数不必做任何事情,因为接口没有任何具体的成员。将函数定义为虚函数和内联函数似乎是矛盾的,但请相信我,事实并非如此。

    class IDemo
    {
        public:
            virtual ~IDemo() {}
            virtual void OverrideMe() = 0;
    };
    
    class Parent
    {
        public:
            virtual ~Parent();
    };
    
    class Child : public Parent, public IDemo
    {
        public:
            virtual void OverrideMe()
            {
                //do stuff
            }
    };
    

    您不必为虚拟析构函数包含主体-事实证明,有些编译器在优化空析构函数时遇到问题,最好不要使用默认值。

        2
  •  230
  •   Warty    8 年前

    用纯虚拟方法生成类。通过创建另一个重写这些虚拟方法的类来使用接口。

    纯虚方法是一个类方法,它被定义为虚方法并分配给0。

    class IDemo
    {
        public:
            virtual ~IDemo() {}
            virtual void OverrideMe() = 0;
    };
    
    class Child : public IDemo
    {
        public:
            virtual void OverrideMe()
            {
                //do stuff
            }
    };
    
        3
  •  140
  •   Raedwald    7 年前

    除了C中的抽象基类之外,还有一个特殊的接口类型类别的全部原因#/ Java 是因为c/j/java不支持多重继承。

    C++支持多重继承,因此不需要特殊类型。一个抽象的基类,没有非抽象(纯虚拟)方法在功能上相当于一个C/J/Java接口。

        4
  •  46
  •   Morgoth Jerry Coffin    7 年前

    C++中没有“接口”本身的概念。AFIK,首先在Java中引入接口,以解决缺乏多重继承的问题。这个概念被证明是非常有用的,并且在C++中通过使用抽象基类可以实现同样的效果。

    抽象基类是一个类,其中至少一个成员函数(Java LINGO方法)是使用以下语法声明的纯虚函数:

    class A
    {
      virtual void foo() = 0;
    };
    

    无法实例化抽象基类,即不能声明类A的对象。只能从派生类,但任何不提供 foo() 也将是抽象的。为了停止抽象,派生类必须为它继承的所有纯虚拟函数提供实现。

    请注意,抽象基类可以不仅仅是接口,因为它可以包含非纯虚拟的数据成员和成员函数。一个接口的等价物将是一个没有任何数据的抽象基类,只有纯虚拟函数。

    而且,正如MarkRansom指出的那样,抽象基类应该为此提供一个虚拟析构函数,就像任何基类一样。

        5
  •  41
  •   Boris Dalstein    11 年前

    就我所能测试的而言,添加虚拟析构函数是非常重要的。我使用的对象是 new 并被摧毁 delete .

    如果不在接口中添加虚拟析构函数,则不会调用继承类的析构函数。

    class IBase {
    public:
        virtual ~IBase() {}; // destructor, use it to call destructor of the inherit classes
        virtual void Describe() = 0; // pure virtual method
    };
    
    class Tester : public IBase {
    public:
        Tester(std::string name);
        virtual ~Tester();
        virtual void Describe();
    private:
        std::string privatename;
    };
    
    Tester::Tester(std::string name) {
        std::cout << "Tester constructor" << std::endl;
        this->privatename = name;
    }
    
    Tester::~Tester() {
        std::cout << "Tester destructor" << std::endl;
    }
    
    void Tester::Describe() {
        std::cout << "I'm Tester [" << this->privatename << "]" << std::endl;
    }
    
    
    void descriptor(IBase * obj) {
        obj->Describe();
    }
    
    int main(int argc, char** argv) {
    
        std::cout << std::endl << "Tester Testing..." << std::endl;
        Tester * obj1 = new Tester("Declared with Tester");
        descriptor(obj1);
        delete obj1;
    
        std::cout << std::endl << "IBase Testing..." << std::endl;
        IBase * obj2 = new Tester("Declared with IBase");
        descriptor(obj2);
        delete obj2;
    
        // this is a bad usage of the object since it is created with "new" but there are no "delete"
        std::cout << std::endl << "Tester not defined..." << std::endl;
        descriptor(new Tester("Not defined"));
    
    
        return 0;
    }
    

    如果运行前一个代码时没有 virtual ~IBase() {}; ,你会看到析构函数 Tester::~Tester() 从未打过电话。

        6
  •  31
  •   Jason Plank IEnumerator    13 年前

    我的回答与其他人基本相同,但我认为还有两件重要的事情要做:

    1. 在您的接口中声明一个虚拟析构函数,或者创建一个受保护的非虚拟析构函数,以避免在有人试图删除类型的对象时出现未定义的行为。 IDemo .

    2. 使用虚拟继承来避免多重继承的问题。(当我们使用接口时,通常会有多个继承。)

    和其他答案一样:

    • 用纯虚拟方法生成类。
    • 通过创建另一个重写这些虚拟方法的类来使用接口。

      class IDemo
      {
          public:
              virtual void OverrideMe() = 0;
              virtual ~IDemo() {}
      }
      

      class IDemo
      {
          public:
              virtual void OverrideMe() = 0;
          protected:
              ~IDemo() {}
      }
      

      class Child : virtual public IDemo
      {
          public:
              virtual void OverrideMe()
              {
                  //do stuff
              }
      }
      
        7
  •  10
  •   Community dbr    7 年前

    在C++ 11中,您可以完全避免继承:

    struct Interface {
      explicit Interface(SomeType& other)
      : foo([=](){ return other.my_foo(); }), 
        bar([=](){ return other.my_bar(); }), /*...*/ {}
      explicit Interface(SomeOtherType& other)
      : foo([=](){ return other.some_foo(); }), 
        bar([=](){ return other.some_bar(); }), /*...*/ {}
      // you can add more types here...
    
      // or use a generic constructor:
      template<class T>
      explicit Interface(T& other)
      : foo([=](){ return other.foo(); }), 
        bar([=](){ return other.bar(); }), /*...*/ {}
    
      const std::function<void(std::string)> foo;
      const std::function<void(std::string)> bar;
      // ...
    };
    

    在这种情况下,接口具有引用语义,即必须确保对象比接口长(也可以使用值语义创建接口)。

    这些类型的接口有其优缺点:

    最后,继承是复杂软件设计中万恶之源。在 Sean Parent's Value Semantics and Concepts-based Polymorphism (强烈推荐,这里解释了此技术的更好版本)研究了以下情况:

    假设我有一个应用程序,其中我使用 MyShape 接口:

    struct MyShape { virtual void my_draw() = 0; };
    struct Circle : MyShape { void my_draw() { /* ... */ } };
    // more shapes: e.g. triangle
    

    在应用程序中,使用 YourShape 接口:

    struct YourShape { virtual void your_draw() = 0; };
    struct Square : YourShape { void your_draw() { /* ... */ } };
    /// some more shapes here...
    

    现在假设您想使用我在应用程序中开发的一些形状。从概念上讲,我们的形状具有相同的界面,但要使我的形状在您的应用程序中工作,您需要将我的形状扩展如下:

    struct Circle : MyShape, YourShape { 
      void my_draw() { /*stays the same*/ };
      void your_draw() { my_draw(); }
    };
    

    首先,修改我的形状可能根本不可能。此外,多重继承导致了通往意大利面条代码的道路(假设第三个项目是使用 TheirShape 界面…如果他们也调用了他们的draw函数,会发生什么? my_draw ?).

    更新:关于非继承的多态性有几个新的参考:

        8
  •  9
  •   Rodyland    16 年前

    以上都是好答案。 你应该记住一件额外的事情——你也可以有一个纯粹的虚拟析构函数。唯一的区别是您仍然需要实现它。

    困惑的?

    
        --- header file ----
        class foo {
        public:
          foo() {;}
          virtual ~foo() = 0;
    
          virtual bool overrideMe() {return false;}
        };
    
        ---- source ----
        foo::~foo()
        {
        }
    
    

    您希望这样做的主要原因是,如果您像我一样希望提供接口方法,但是使覆盖它们成为可选的。

    要使类成为接口类,需要纯虚方法,但所有虚方法都有默认实现,因此唯一剩下的纯虚方法是析构函数。

    在派生类中重新实现一个析构函数一点也不重要——我总是在派生类中重新实现一个析构函数,不管是虚拟的还是非虚拟的。

        9
  •  7
  •   Cody Gray BugGuyBCS    8 年前

    如果你使用微软的C++编译器,那么你可以做到以下几点:

    struct __declspec(novtable) IFoo
    {
        virtual void Bar() = 0;
    };
    
    class Child : public IFoo
    {
    public:
        virtual void Bar() override { /* Do Something */ }
    }
    

    我喜欢这种方法,因为它会导致更小的接口代码,并且生成的代码大小可以明显更小。使用novtable将删除该类中对vtable指针的所有引用,因此您永远不能直接实例化它。请参阅此处的文档- novtable .

        10
  •  4
  •   Uri    16 年前

    除了上面写的内容:

    首先,确保析构函数也是纯虚拟的

    第二,在实现时,您可能希望实际上(而不是通常)继承,只是为了好的度量。

        11
  •  4
  •   Luc Hermitte    13 年前

    您还可以考虑使用Nvi(非虚拟接口模式)实现的契约类。例如:

    struct Contract1 : boost::noncopyable
    {
        virtual ~Contract1();
        void f(Parameters p) {
            assert(checkFPreconditions(p)&&"Contract1::f, pre-condition failure");
            // + class invariants.
            do_f(p);
            // Check post-conditions + class invariants.
        }
    private:
        virtual void do_f(Parameters p) = 0;
    };
    ...
    class Concrete : public Contract1, public Contract2
    {
    private:
        virtual void do_f(Parameters p); // From contract 1.
        virtual void do_g(Parameters p); // From contract 2.
    };
    
        12
  •  1
  •   Yeo    9 年前

    在C++开发中,我还是新手。我从Visual Studio(vs)开始。

    然而,似乎没有人提到 __interface 在VS中 (.net) . 我是 非常确定这是否是声明接口的好方法。但它似乎提供了 附加强制执行 (提到) the documents )这样您就不必显式地指定 virtual TYPE Method() = 0; ,因为它将自动转换。

    __interface IMyInterface {
       HRESULT CommitX();
       HRESULT get_X(BSTR* pbstrName);
    };
    

    但是,我不使用它,因为我担心跨平台编译的兼容性,因为它只在.NET下可用。

    如果有人对此有兴趣,请分享。-)

    谢谢。

        13
  •  0
  •   lorro    8 年前

    虽然这是真的 virtual 是定义接口的事实上的标准,让我们不要忘记C++中的构造函数,即经典的C类模式。

    struct IButton
    {
        void (*click)(); // might be std::function(void()) if you prefer
    
        IButton( void (*click_)() )
        : click(click_)
        {
        }
    };
    
    // call as:
    // (button.*click)();
    

    这样做的好处是,您可以重新绑定事件运行时而不必重新构建类(因为C++没有改变多态类型的语法,这是变色龙类的一种解决方法)。

    提示:

    • 您可以将此继承为基类(允许虚拟和非虚拟),并填充 click 在后代的构造函数中。
    • 您可以将函数指针作为 protected 成员并拥有 public 参考和/或吸气剂。
    • 如上所述,这允许您在运行时切换实现。因此,它也是管理状态的一种方法。取决于 if s与代码中的状态更改,这是 可以 比快 switch() ES或 如果 S(预计周转时间约为3-4 如果 S,但总是先测量。
    • 如果你选择 std::function<> 在函数指针上,您 可以 能够管理内部的所有对象数据 IBase . 从这一点上,您可以得到 iBASE (例如, std::vector<IBase> 将工作)。注意这个 可以 根据编译器和STL代码的不同,速度要慢一些;另外,当前的 标准::功能<> 与函数指针甚至虚拟函数相比,通常会有开销(这在将来可能会改变)。
        14
  •  0
  •   Chen Li    7 年前

    以下是对 abstract class 在C++标准中

    N468

    3.4.2

    抽象类是只能用作其他类的基类的类;没有抽象对象 可以创建类,但作为派生自该类的类的子对象除外。类至少有一个 一个纯虚拟函数。

        15
  •  -2
  •   hims    11 年前
    class Shape 
    {
    public:
       // pure virtual function providing interface framework.
       virtual int getArea() = 0;
       void setWidth(int w)
       {
          width = w;
       }
       void setHeight(int h)
       {
          height = h;
       }
    protected:
        int width;
        int height;
    };
    
    class Rectangle: public Shape
    {
    public:
        int getArea()
        { 
            return (width * height); 
        }
    };
    class Triangle: public Shape
    {
    public:
        int getArea()
        { 
            return (width * height)/2; 
        }
    };
    
    int main(void)
    {
         Rectangle Rect;
         Triangle  Tri;
    
         Rect.setWidth(5);
         Rect.setHeight(7);
    
         cout << "Rectangle area: " << Rect.getArea() << endl;
    
         Tri.setWidth(5);
         Tri.setHeight(7);
    
         cout << "Triangle area: " << Tri.getArea() << endl; 
    
         return 0;
    }
    

    结果: 矩形面积:35 三角形区域:17

    我们已经看到一个抽象类是如何用getArea()定义一个接口的,另外两个类实现了相同的函数,但是使用不同的算法来计算特定于形状的区域。