【问题标题】:C++ protected constructor for abstract classes抽象类的 C++ 保护构造函数
【发布时间】:2015-11-17 01:43:40
【问题描述】:

我试图找出抽象基类的公共构造函数和受保护构造函数之间的区别。 假设我有两个类:

抽象基类:怪物

class Monster
{
public: // or protected?? what is the techincal difference?
    Monster(string name)
    {
        _name = name;
    }
public:
    virtual void attack() = 0;

    const string getName() const
    {
        return _name;
    }

private:
    string _name;

};

具体的子类:Spider

class Spider : public Monster
{
public:
    Spider(string name) : Monster(name)
    {

    }

    void attack() override
    {
        cout << getName() << ":(Spider) is attacking!" << endl;
    }
};

现在如果我尝试创建一个 Monster 实例:

int main()
{
    Monster monster1 { "Abstract Monster Not Allowed" }; // Error
}

错误: 不允许抽象类类型“怪物”的对象: Monster:: Attack 是一个纯虚函数。

这完全有道理,但是“公共构造函数”是抽象类中“受保护构造函数”的别名吗? 在什么情况下我可以为抽象类使用受保护的构造函数?

提前致谢。

【问题讨论】:

标签: c++ constructor


【解决方案1】:

将硬币视为抽象基类。您不想直接实例化硬币对象,但您会想要构造具有不同属性(例如原产国、面值)的衍生硬币类型,然后相应地设置属性,例如它们的大小、重量和材料的。我们不希望在某个容器中放置一堆硬币基类,因为没有足够的信息来描述该硬币对象。因此这个硬币对象是抽象的或者是一个概念。然而,这个硬币基类的派生对象,例如美国银元或德国法郎等,是可以实例化的真实对象,因为它们是真实对象,并且在构造它们时已经了解了足够的信息。

由于 this 的性质以及类封装的工作原理,即使基类不是抽象的,这意味着不需要纯虚函数,那么硬币基类的构造函数应该受到保护!即使您确实需要在这个硬币类中使用纯虚拟方法,保护构造函数仍然是一种好习惯。这使得其他人在查看您的类接口并看到受保护的构造函数时对源代码更具可读性。他们知道这个类不能直接实例化。

现在让我们说另一个类具有“硬币”类的关系,例如银行类或铸币类,其中铸币类创建硬币而银行类持有硬币,这些类可能可以对受保护的硬币构造函数使用朋友访问修饰符,以便他们可以在任何信息可用之前创建这些硬币的实例,并将它们存储到容器中以供以后处理。将此视为预批处理操作,并且使用此布局,硬币基类用作模板(不是 c++ 编程模板)。

【讨论】:

  • 谢谢,非常澄清的评论,我不知道为什么我需要用朋友修饰符实例化抽象硬币(总是如果它没有纯虚函数)..如果在我创建的银行类中一个硬币(抽象对象),在我必须销毁该对象并重新初始化或加上正确重新指向新的 Specialized 具体类之后..
【解决方案2】:

抽象类中的protected构造函数只能在构造派生类实例的过程中调用。

但是,main() 中的错误消息与受保护的构造函数无关。这是因为一个抽象类(至少有一个函数声明为纯虚函数,就像你对virtual void attack() = 0 所做的那样)不能被实例化,不管它有什么构造函数和可访问性(protectedpublicprivate) 的那些构造函数。

一个派生类也不能被实例化,除非它覆盖它继承的所有纯虚函数。如果它覆盖了所有继承的纯虚函数(并且没有将它自己的任何函数声明为纯虚函数),它就可以被实例化。

【讨论】:

    【解决方案3】:

    在使用继承和抽象基类时,如果你的基类有一个纯虚函数virtual void doSomething() = 0;,这意味着从这个基类继承的每个类都必须实现这个函数;另外,这个基类不能被实例化,因为它是抽象的。

    在使用构造函数进行公共、受保护和私有成员访问时,将限制类对外部调用者的行为方式。如果构造函数是公共的,则可以调用该构造函数并创建该对象的实例,前提是该类不是抽象的。当构造函数受到保护时,不能从类外部调用构造函数;但是,任何从它继承的类都可以调用受保护的构造函数,除非使用了朋友修饰符。

    这就是抽象的意思。抽象的基类不能是对象,而是一个封装的概念或想法,包含所有派生类之间共有的所有信息。它是可以实例化的对象的子对象。

    如果您在本例中使用纯虚拟方法,则无法实例化基类,但是如果您仅使用虚拟方法而没有纯虚拟方法,则可以实例化它!但是要小心基类中的虚方法并在构造函数中调用它们,它们可能很危险。

    我将展示一个包含公共和受保护构造函数的抽象基类的简单示例。第一种情况是使用公共构造函数:

    class Animal {
    public: 
        enum AnimalType {
            AMPHIBIAN,
            BIRD,
            FISH,
            MAMMAL,
            REPTILE,
         }; // AnimalType
            
    protected:
        AnimalType m_type;
        int m_age;
        float m_weight;
    
        std::string m_strVoice;
    
    public:
        Animal( AnimalType type, int age, float weight ) :
            m_type( type ), m_age( age ), m_weight( weight )
        {}
    
        virtual ~Animal() {}  // Virtual Due To Inheritance
        
        AnimalType getType() const { return m_type; }
    
        void setAge( int age ) { m_age = age; }
        int  getAge() const { return m_age; }
    
        void setWeight( float weight ) { m_weight = weight; }
        float getWeight() const { return m_weight; }
    
        virtual std::string speak() = 0; // Purely Virtual All Derived Class Must Implement This
        // Abstract Class Can Not Be Instantiated
    
    }; // Animal
    
    class Dog : public Animal {
    public:
        Dog( int age, float weight ) : Animal( AnimalType::MAMMAL, age, weight ) {}
    
        virtual ~Dog() {}
    
        std::string speak() { return std::string( "rough" ); }
    
    }; // Dog
    

    使用这种结构,您只能在源代码的其他位置创建 Dog 对象。

    现在用上面的例子来演示一个受保护的构造函数:

    class Animal {
    public: 
        enum AnimalType {
            AMPHIBIAN,
            BIRD,
            FISH,
            MAMMAL,
            REPTILE,
         }; // AnimalType
            
    protected:
        AnimalType m_type;
        int m_age;
        float m_weight;
    
        std::string m_strVoice;
    
    public:
        virtual ~Animal() {}  // Virtual Due To Inheritance
        
        AnimalType getType() const { return m_type; }
    
        void setAge( int age ) { m_age = age; }
        int  getAge() const { return m_age; }
    
        void setWeight( float weight ) { m_weight = weight; }
        float getWeight() const { return m_weight; }
    
        virtual std::string speak() = 0; // Purely Virtual All Derived Class Must Implement This
        // Abstract Class Can Not Instantiate
    
    protected:
        Animal( AnimalType type, int age, float weight ) :
            m_type( type ), m_age( age ), m_weight( weight ) {}
        // Constructor Is Protected - Doesn't Necessarily Make It Abstract
        // But Prevents Outside Code From Accessing This Constructor Only
        // Allowing Either Derived Classes Or Friends To Access This Constructor 
    
    }; // Animal
    
    class Mammal : public Animal {
    public:
        virtual std:string speak() = 0;
    protected:
        Mammal( int age, float weight ) : Animal( AnimalType::MAMMAL, age, weight ) {}     
    
    }; // Mammal
    
    class Dog : public Mammal {
    public:
        Dog( int age, float weight ) : Mammal( age, weight ) {}
    
        virtual ~Dog() {}
    
        std::string speak() { return std::string( "rough" ); }
    
    }; // Dog
    

    在这里的第二种情况下,Animal 和 Mammal 都不能从其他地方构造为对象,但 Dog 可以。这是由于受保护的构造函数。这里 Animal 和 Mammal 都代表一个概念而不是一个对象,但 Dog 确实代表一个对象。希望对您有所帮助。

    【讨论】:

    • 这是不正确的!我知道“抽象”概念,但在你的例子中,你不能创建 Animal 的对象,尽管它有一个公共构造函数!我的问题是:知道不能实例化具有至少一个纯虚函数的抽象类(这会导致编译错误),那么保护抽象类构造函数有什么好处?在这两种情况下(具有纯虚函数的抽象类的公共或受保护构造函数),您都不能创建该抽象类型的对象。演示:ERROR-Demostration
    • 你说得对,很抱歉我在基类中使用了纯虚函数,而不仅仅是虚函数!
    • 如果你知道你的基类将有一个纯虚方法,该类不能被实例化,那么最好有一个受保护的构造函数,这样只有派生类才能访问它!
    • 另外,如果你的基类有一个私有构造函数,那么你的派生类将需要对其类使用friend修饰符才能访问私有构造函数。
    • 我不知道编译器如何在机器级别解释它的技术细节,但我知道的是,当涉及到公共、受保护和私有时,它们是两个类的访问修饰符&结构。考虑一个公共函数与受保护函数以及谁可以访问这些函数,那么您的构造函数就是一个函数!虽然是特殊功能!它仍然以相同的方式工作! ...
    【解决方案4】:

    在 C++ 中,公共类成员可以从任何地方访问,但受保护的成员只能从派生类访问。私有成员只能从类本身访问,因此任何派生类都无法访问它们。

    这里有更多关于protected constructors的信息。

    【讨论】:

    • 不回答问题。
    猜你喜欢
    • 2010-11-08
    • 2013-11-13
    • 2023-03-04
    • 2013-08-29
    • 1970-01-01
    • 2011-08-01
    • 2011-05-30
    • 2018-06-10
    • 2012-08-18
    相关资源
    最近更新 更多