【问题标题】:Using an abstract class in C++在 C++ 中使用抽象类
【发布时间】:2009-03-16 11:53:58
【问题描述】:

我在将扩展对象作为参数传递给函数时尝试使用抽象类,但到目前为止我的尝试导致了一些编译器错误。

我有一些关于问题所在的线索,我显然不允许实例化抽象类,而且我相信 MyClass 中的一些代码正在尝试这样做,即使这不是我的意图。一些研究表明我应该将该对象作为指针引用来实现我想要的,但到目前为止我的尝试都失败了,我什至不确定这是不是答案(因此我在这里问)。

现在我将提交,因为我对 Java 比对 C++ 更熟悉,而且我确信我的部分问题是由于这个原因。

这是我在程序中尝试做的一个示例:

class A {
    public:
        virtual void action() = 0;
};

class B : public A {
    public:
        B() {}

        void action() {
            // Do stuff
        }
};

class MyClass {

    public:

        void setInstance(A newInstance) {
            instance = newInstance;
        }

        void doSomething() {
            instance.action();
        }

    private:

        A instance;
};

int main(int argc, char** argv) {
    MyClass c;
    B myInstance;
    c.setInstance(myInstance);
    c.doSomething();
    return 0;
}

此示例产生的编译器错误与我在程序中遇到的相同:

sean@SEAN-PC:~/Desktop$ gcc -o test test.cpp
test.cpp:20: error: cannot declare parameter ‘newInstance’ to be of abstract type ‘A’
test.cpp:2: note:   because the following virtual functions are pure within ‘A’:
test.cpp:4: note:   virtual void A::action()
test.cpp:30: error: cannot declare field ‘MyClass::instance’ to be of abstract type ‘A’
test.cpp:2: note:   since type ‘A’ has pure virtual functions
test.cpp: In function ‘int main(int, char**)’:
test.cpp:36: error: cannot allocate an object of abstract type ‘A’
test.cpp:2: note:   since type ‘A’ has pure virtual functions

更新

感谢大家的反馈。

我已经更改了“MyClass::instance 以包含 A 类型的指针,但我现在遇到了一些与 vtable 相关的奇怪错误:

sean@SEAN-PC:~/Desktop$ gcc -o test test.cpp
/tmp/ccoEdRxq.o:(.rodata._ZTI1B[typeinfo for B]+0x0): undefined reference to `vtable for __cxxabiv1::__si_class_type_info'
/tmp/ccoEdRxq.o:(.rodata._ZTI1A[typeinfo for A]+0x0): undefined reference to `vtable for __cxxabiv1::__class_type_info'
/tmp/ccoEdRxq.o:(.rodata._ZTV1A[vtable for A]+0x8): undefined reference to `__cxa_pure_virtual'
collect2: ld returned 1 exit status

我的修改代码如下(A和B都没有修改):

class MyClass {

    public:

        void setInstance(A* newInstance) {
            instance = newInstance;
        }

        void doSomething() {
            instance->action();
        }

    private:

        A* instance;
};

int main(int argc, char** argv) {
    MyClass c;
    B myInstance;
    c.setInstance(&myInstance);
    c.doSomething();
    return 0;
}

【问题讨论】:

    标签: c++ abstract-class parameters


    【解决方案1】:

    您的问题是您应该在函数中接受引用。原因是引用实际上并没有复制传递的参数。但是,如果您接受A - 而不是引用A& - 那么您实际上将传递的参数复制到参数对象中,您得到的是A 类型的对象 - 但实际上是不允许的!

        // the reference parameter will reference the actual argument
        void setInstance(A &newInstance) {
                // assign the address of the argument to the pointer member
                // instance. 
                instance = &newInstance;
        }
    

    然后您必须将类中的成员更改为指针。它不能是一个引用,因为setInstance 会改变它所引用的内容——一个引用在其整个生命周期内只能引用一个对象,而一个指针可以通过重新分配一个不同的地址来设置指向做不同的事情。那么剩下的部分是这样的

        void doSomething() {
            // call a member function on the object pointed to
            // by instance!
            instance->action();
        }
    
    private:
    
        // a pointer to some object derived from A
        A *instance;
    

    另请注意,您必须使用 g++ 编译 C++ 程序,因为它还会将 C++ 标准库链接到您的代码

    g++ -o test test.cpp # instead of gcc!
    

    【讨论】:

    • 太好了,我的第二个 vtable 问题是由于使用 gcc 而不是 g++。谢谢!
    • 另外,如果您打算通过“A”指针删除“B”对象,则将 A::~A 定义为虚拟。
    【解决方案2】:

    您所做的将在 Java 中起作用,因为声明“A”类型的参数或成员变量实际上意味着“指向 A 的指针”。在 C++ 中,您实际上需要明确这一点,因为它们是两个不同的东西:

    void setInstance(A* newInstance) { // pointer to an "A"
                    instance = newInstance;
    }
    

    并在声明中:

    A* instance; // Not an actual "A", but a pointer to an "A"
    

    【讨论】:

    • @n0rd - 糟糕,我打算使用引用,然后在注意到他的声明不能作为引用(引用不能为空)后切换到指针。我会编辑和修复它。
    【解决方案3】:

    您现在的问题是链接。 对于 C++ 程序,必须添加标准 C++ 库:

    gcc -o test -lstdc++ test.cpp

    【讨论】:

    • 你真的应该在这里使用 g++ 而不是 gcc。它不仅仅是 -lstdc++。
    【解决方案4】:

    我相信这就是你想要做的。它通过根据句柄类是指向 B 还是 C 的实例实际打印出一些东西来演示多态性。其他是正确的,您可能还需要一个虚拟析构函数。

    编译:g++ test.cpp -o Test

    #include <stdio.h>
    
    class A {
        public:
            virtual void action() = 0;
    };
    
    class B : public A {
        public:
            B() {}
    
            void action() {
                    printf("Hello World\n");
            }
    };
    
    class C : public A {
        public:
            C() {}
    
            void action() {
                    printf("Goodbye World\n");
            }
    };
    
    class AHandleClass {
    
        public:
    
            void setInstance(A *A_Instance) {
                    APointer = A_Instance;
            }
    
            void doSomething() {
                    APointer->action();
            }
    
        private:
    
            A *APointer;
    };
    
    int main(int argc, char** argv) {
        AHandleClass AHandle;
        B BInstance;
        C CInstance;
        AHandle.setInstance(&BInstance);
        AHandle.doSomething();
        AHandle.setInstance(&CInstance);
        AHandle.doSomething();
        return 0;
    }
    

    【讨论】:

    • 感谢您发布此内容,我尝试对其进行编译,但它会产生与我当前在更新代码中遇到的相同的 vtable 错误。有什么想法吗?
    • 最后我不小心使用了 -> 当我应该使用 .它现在可以编译,我添加了一些内容以使其更具信息性。
    • 这很好用,因为它在 main 上,但在其他情况下,如果 AHandle 超过了声明 BInstanceCInstance 的范围,则引用将变为无效并且将发生内存损坏
    【解决方案5】:

    您应该将 A 存储为指针。

    A* instance;
    

    编辑:我之前写过“参考”。 C++ 有区别。

    【讨论】:

    • 那不是一个引用,它是一个指针。请编辑您的答案。
    【解决方案6】:

    如果您退出 setter 并使用构造函数,则不必使用指针。这是 C++ 的重要特性之一:构造函数中的基初始化器通常允许避免使用指针。

    class MyClass {
    
            public:
    
                    MyClass(A & newInstance) : instance(newInstance) {
                    }
    
                    void doSomething() {
                            instance.action();
                    }
    
            private:
    
                    A & instance;
    };
    
    
    
    int main(int argc, char** argv) {
            B myInstance;
            MyClass c(myInstance);
    

    【讨论】:

    • 我打算发布一些接近这个的东西。与原始方法的主要区别在于,在 litb 的原始版本中,可以随时调用 setter 更改实例。使用引用,一旦构造了对象,它就不能“指向”不同的“实例”
    【解决方案7】:

    我在iostream 之前添加parent.h 遇到了这个问题:

    错误:

    include "parent.h"
    include <iostream>
    

    右:

    include <iostream>
    include "parent.h"
    

    【讨论】:

      【解决方案8】:

      Johannes Schaub - litb 是正确的。

      在 C++ 中,抽象类不能用作函数的参数或返回类型。我们无法实例化抽象对象。

      所以需要使用&或*。

      【讨论】:

        【解决方案9】:

        您必须使用指向 A 的指针作为 MyClass 的成员。

        class MyClass {
        
            public:
        
                void setInstance(A *newInstance) {
                        instance = newInstance;
                }
        
                void doSomething() {
                        instance->action();
                }
        
            private:
        
                A *instance;
        };
        

        如果您不这样做,MyClass 构造函数将尝试实例化一个 A 对象(就像对任何成员对象一样),这是不可能的,因为 A 是抽象的。

        【讨论】:

          【解决方案10】:

          当你说

          A instance;
          

          你将创建一个 A 类型的新对象。但是你已经说过 A 是一个抽象类,所以你不能那样做。正如其他几个人所指出的那样,您需要使用指针,或者使 A 非抽象。

          【讨论】:

            【解决方案11】:

            Dmitry 是正确的,如果你使用 gcc,你应该使用 -lstdc++,但更好的是使用g++。 (相同的语法)。
            另外,您会注意到(我猜如果您添加 -Wall)您会收到警告,表明您的带有虚函数的类没有析构函数,因此最好也向 A 添加一个(虚拟)析构函数。

            【讨论】:

              猜你喜欢
              • 2011-09-06
              • 2018-12-28
              • 2010-10-15
              • 1970-01-01
              • 1970-01-01
              • 2013-11-29
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多