首页 文章

你如何在C中声明一个接口?

提问于
浏览
748

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

15 回答

  • 31

    您还可以考虑使用NVI(非虚拟接口模式)实现的 Contract 类 . 例如:

    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.
    };
    
  • 642

    对那里写的内容有点补充:

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

    其次,您可能希望在实施时虚拟(而不是正常)继承,只是为了获得良好的衡量标准 .

  • 9

    要通过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
            }
    };
    

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

  • 9

    到目前为止我可以测试,添加虚拟析构函数非常重要 . 我正在使用 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() .

  • -2

    我还是C开发的新手 . 我从Visual Studio(VS)开始 .

    然而,似乎没有人提到VS _107161中的 __interface . 我是 not 非常肯定这是否是一个声明接口的好方法 . 但它似乎提供了额外的执法(在the documents中提到) . 这样您就不必显式指定 virtual TYPE Method() = 0; ,因为它会自动转换 .

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

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

    如果有人有任何有趣的事情,请分享 . :-)

    谢谢 .

  • 4

    使用纯虚方法创建一个类 . 通过创建覆盖这些虚拟方法的另一个类来使用该接口 .

    纯虚方法是一种定义为虚拟并分配给0的类方法 .

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

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

    C支持多重继承,因此不需要特殊类型 . 没有非抽象(纯虚拟)方法的抽象基类在功能上等同于C#/ Java接口 .

  • 40

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

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

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

    和其他答案一样:

    • 使用纯虚方法创建一个类 .

    • 通过创建覆盖这些虚拟方法的另一个类来使用该接口 .

    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
            }
    }
    
  • 4

    如果您使用的是Microsoft的C编译器,那么您可以执行以下操作:

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

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

  • 228

    以下是c标准中 abstract class 的定义

    n4687

    13.4.2

    抽象类是一个只能用作其他类的基类的类;除了作为派生自它的类的子对象之外,不能创建抽象类的任何对象 . 如果一个类至少有一个纯虚函数,则它是抽象的 .

  • 7

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

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

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

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

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

    而且,正如Mark Ransom指出的那样,抽象基类应该像任何基类一样提供虚拟析构函数 .

  • 135

    虽然 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 引用和/或getter .

    • 如上所述,这允许您在运行时切换实现 . 因此,这也是管理国家的一种方式 . 根据代码中 if 与状态变化的数量,这可能比 switch() es或 if 更快(预计周转时间为3-4 if ,但始终先测量 .

    • 如果在函数指针上选择 std::function<> ,则可以在 IBase 中管理所有对象数据 . 从这一点开始,您可以获得 IBase 的值示意图(例如, std::vector<IBase> 将起作用) . 请注意,这可能会更慢,具体取决于您的编译器和STL代码;此外,当与函数指针甚至虚函数相比时, std::function<> 的当前实现往往会产生开销(这可能在将来发生变化) .

  • 0
    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()定义接口,另外两个类实现相同的函数,但使用不同的算法来计算特定于形状的区域 .

  • 0

    在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 接口......如果它们也调用它们的绘制函数 my_draw 会发生什么?) .

    更新:有一些关于基于非继承的多态性的新引用:

  • 1

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

    困惑?

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

    您想要这样做的主要原因是,如果您想提供接口方法,就像我一样,但要将它们覆盖为可选项 .

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

    在派生类中重新实现析构函数根本没什么大不了的 - 我总是在派生类中重新实现析构函数,无论是否为虚函数 .

相关问题