【问题标题】:Is a public constructor in an abstract class a codesmell? [closed]抽象类中的公共构造函数是代码气味吗? [关闭]
【发布时间】:2010-11-24 16:34:18
【问题描述】:

抽象类中的公共构造函数是代码气味吗?使构造函数受保护提供了您可以使用的所有访问权限。将其公开将提供的唯一额外访问是允许将类的实例声明为无法访问其受保护成员的范围内的变量,但根本不能声明抽象类的实例。

【问题讨论】:

  • 在显式声明/定义您的受保护/私有复制构造函数时要小心。如果你不这样做,编译器会为你做。请参阅 scott meyers effective c++ 第 6 项。
  • 如何声明抽象类的变量?我错过了什么吗?
  • 我认为OP意味着方法,如protected member methods.
  • 我还是不明白。你能举个例子吗?
  • @sbi:编辑了我的答案以显示代码的一些问题。

标签: c++


【解决方案1】:

我在至少一个编码指南中读到过,抽象类的构造函数不应该是公共的——我认为这条规则是有道理的,因为你给出的原因。

但是,我无法想象公开它会导致事情出错的场景。所以我不会说这是代码味道。我认为受保护的构造函数是一个“值得拥有”的属性:)

【讨论】:

  • 同意,受保护的构造函数明确了类不能直接实例化的事实,因此从程序员的角度来看,它是一个“不错的属性”,即使编译器并不真正关心它。
  • 编码指南应该保护你免受可能发生的坏事的影响。由于您无法实例化抽象类,因此我看不出该指南可以保护您免受什么影响,因此感觉是任意的。
  • 他们说,通过保护它,你明确表示它不能被派生类使用。我怀疑这与成员运算符 new 和 delete 的基本原理相似:它们是隐式静态成员,但最好使它们显式静态。另一方面,成员函数的类内定义是隐式内联的,我想说让它们显式内联很糟糕。最后,我认为这个受保护的构造函数问题是一个品味问题。
  • 抽象类中的公共构造函数破坏了swig的默认操作。
【解决方案2】:

我的观点是,公共构造函数可能会让人感到困惑,正如您所说,将其设为受保护是正确的。我想说,受保护的构造函数正确地强化了抽象类的唯一合理用途是从它派生的印象。

事实上,你只需要在抽象类中声明一个构造函数,如果它需要做某事,例如。初始化自己的私有成员。然后我希望会有其他对派生类有帮助的受保护成员函数。

编辑:

由于没有人发布任何代码并且@sbi 在对 OP 的评论中要求提供一些代码,我想我会发布一些:

class Base:
{
public:           // The question is: should the ctor be public or protected?
// protected:
    Base():i(0){} // ctor is necessary to initialise private member variable
public:
    virtual ~Base(){} // dtor is virtual (but thats another story)
                  // pure virtual method renders the whole class abstract
    virtual void setValue(void)=0;  
protected:
    int getValue(void){ return i;}
private:
    int i;
};

Base b1;  // Illegal since Base is abstract, even if ctor is public
Base& b2=makeBase();  //We can point to, or refer to a Base
b2.setValue();    // We're not sure what this does, but we can call it.
b2.getValue();    // Illegal since getValue is protected

【讨论】:

    【解决方案3】:

    正如你所说,抽象类的构造函数不需要提供公共构造函数,如果你提供了公共构造函数,它也不能被滥用。

    但是,您可以考虑将构造函数声明为公共的,作为对从抽象类派生的结构化类的建议。

    【讨论】:

    • 你能解释一下你的第二句话吗?我不明白建议可能是什么。
    • 我认为公共 ctor 会推荐实现类作为设计公共接口的一种方式。就像,一个抽象的“Window”类可以有一个带有参数“Window *parent”的公共ctor。它将推荐派生类以将其作为公共构造函数提供,这将成为接口的一部分。
    • 我认为可能是这种情况 - 我会说它错了。我会说,对于 ctor,它应该受到保护并获取它需要的参数,完全独立于派生类应该做什么。然而,任何其他公共纯虚成员函数应该强烈表明叶类需要使用(显然)相同的访问级别和参数来实现这些。
    【解决方案4】:

    抽象类永远不能直接实例化,因为它声明了纯虚方法。因此,将构造函数声明为受保护只是为程序员增加了额外的提示。

    我在 Google 风格指南中看到了将构造函数声明为受保护的here 的建议。我在其他一些公司编码标准中也遇到过类似的建议。所以这看起来是一个很好的做法。

    【讨论】:

      【解决方案5】:

      出于几个原因,我会说这是一个不好的迹象。

      首先,它可能会误导某人认为他们实际上可以调用该例程。

      第二个是它暗示了作者认为你可以称呼它。你现在不知道这是否是他设计的重要部分。

      【讨论】:

      • 我不认为这些是有效的论点。没有人首先知道 OO 设计或 C++ 会认为您可以调用它,其次编译器会在您尝试时给出错误。
      【解决方案6】:

      我知道很多人显然不同意,但我不认为抽象类 ctor 应该受到保护,除非您正在设计的接口有特定原因让所有派生类也具有受保护的 ctor。

      原因是当您设计一个抽象类时,您就是在设计一个由派生类实现的接口。派生类应该完全按照基类中的设计实现接口。抽象基类呈现的接口是契约;不要违反合同。

      或者换句话说,如果您要更改接口,为什么要从该类派生?

      这是一个有点宗教的答案,但到底是什么。 ;-)

      【讨论】:

      • @Robert:我理解你在这里给出的理由,但我相信它不适用于 ctor。是的,其他公共和受保护的成员函数(不包括 ctor 和 dtor)提供的接口是一个契约,您应该在更改它们之前三思而后行。
      【解决方案7】:

      现代 C++ 的答案

      但是,我在这里碰到了一个旧线程:

      • 这是我在搜索抽象类中publicprotected 构造函数之间的区别时遇到的第一个问题,
      • 在提出并回答此问题后发布的新 C++ 标准更改了“正确”答案。

      总结

      public 抽象类中的构造函数不是代码异味。由于继承构造函数的访问修饰符的工作方式,publicprotected 构造函数是不等价的。抽象类中的public 构造函数比protected 增加了更多的访问权限。


      说明

      标准草案N4659, 10.3.3/19 规定:

      using-declaration 创建的同义词具有通常的可访问性 成员声明。一个 using-declarator 命名一个 构造函数不创建同义词;相反,额外的 如果构造函数在使用时可访问,则它们是可访问的 构造相应基类的对象,然后 using-declaration 的可访问性被忽略

      此行为是在 C++11 中与 using-declarators 一起引入的,有关详细信息,请参阅 this question

      这是一个最小的例子 [godbolt]:

      class AbstractPublicCtor {
      public:
          virtual void foo() = 0;
          AbstractPublicCtor(int) {}
      };
      
      class AbstractProtectedCtor {
      public:
          virtual void foo() = 0;
      protected:
          AbstractProtectedCtor(int) {}
      };
      
      class PublicCtor : public AbstractPublicCtor {
      public:
          using AbstractPublicCtor::AbstractPublicCtor;
          void foo() override {}
      };
      
      class ProtectedCtor : public AbstractProtectedCtor {
      public:
          using AbstractProtectedCtor::AbstractProtectedCtor;
          void foo() override {}
      };
      
      int main() {
          PublicCtor x(5);  // works
          ProtectedCtor y(6); // fails to compile 
      }
      

      经验法则

      • 如果抽象类的实现者应该具有public 构造函数,并且基类构造函数是实现者的public 构造函数的合适候选者,则使用public 构造函数。
      • 否则,请使用 protected 构造函数(例如,当实现者使用工厂构造时)。

      注意:这是我个人的看法,很高兴在 cmets 中讨论。

      【讨论】:

      • 我不同意经验法则和总结,因为它仅适用于派生类不使用自己的构造函数特化并且保护构造函数并且不使用“使用”似乎更具可读性”,我会说将“使用”与“AbstractPublicCtor”结合使用也是代码异味。当构造函数应该只能从派生类调用时,我会将其设置为受保护的,这样意图就很清楚了。
      猜你喜欢
      • 2011-02-14
      • 2010-12-13
      • 2011-05-30
      • 2013-08-29
      • 2014-12-23
      • 2011-06-15
      • 2014-05-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多