什么是在 C++ 中重载构造函数的正确而优雅的方法

What Is a Proper and Elegant Way to Overload Constructors in C++

我在几年前接触过 C++,现在为了我正在从事的项目不得不重新使用它。我学习了大部分基础知识,但从未真正确定 C++ 希望您如何实现其 classes 的想法。

我有使用其他语言的经验,例如 Java、Python 和 C#。

我遇到的一个麻烦是了解如何在 C++ 中重载构造函数以及如何正确地进行继承。

例如,假设我有一个名为 Rect 的 class 和一个继承自 Rect 的名为 Square 的 class。

在Java...

public class Rect {
   protected double m_length, m_width;

   public Rect(double length, double width) {
      this.m_length = length;
      this.m_width = width;
   }

   public double Area()
   {
      return (this.m_length * this.m_width);
   }

}

public class Square extends Rect {
   private String m_label, m_owner;
   public Square(double side) {
      super(side, side);
      this.m_owner = "me";
      this.m_label = "square";
   }

   //Make the default a unit square
   public Square() {
      this(1.0);
      this.m_label = "unit square";
   }
}

然而,在 C++ 中同样的过程让人感觉很复杂。

Headers:

class Rect
{
public:
   Rect(double length, double width) : _length(length), _width(width) { } 
   double Area();

protected:
   double _length, _width;
}

class Square : public Rect 
{
public:
   Square(double side) : 
   Rect(side, side), _owner("me"), _label("square") { }

   Square() : Rect(1.0, 1.0), _owner("me"), _label("unit square");

private:
   std::string _owner, _label;
}

我觉得我不应该再写出 Rect 了。这看起来像是大量的重写,特别是因为在我的项目中我经常使用矩阵并且我希望构造函数能够像 Java 中那样相互扩展以避免重写大量代码。

我确信有一种方法可以正确地做到这一点,但我还没有看到有人真正谈论过这个问题。

是的,在较新版本的 C++ 中,有一种方法可以删除大量样板文件。即使在旧版本的 C++ 中,在您提到的特定情况下,客观上也有比您正在做的更好的方法来处理问题。

class Rect
{
public:
   Rect(double length, double width) : _length(length), _width(width) { } 
   double Area();

protected:
   double _length, _width;
}

class Square : public Rect 
{
public:
   explicit Square(double side = 1.0) : 
   Rect(side, side), _owner("me"), _label("square") { }
private:
   std::string _owner, _label;
}

对于您的情况,默认参数是可行的方法。并且您应该包含关键字 explicit 以避免让编译器自动将 double 值转换为 Square,因为这可能会让人们感到惊讶。默认参数在 C++ 中存在了很长时间,应该适用于所有编译器。

另一种仅适用于 post C++11(此时几乎所有 C++ 编译器)的技术可以帮助解决此类问题,它是 delegating constructors.

一种更有限的技术也是 post C++11 是 inheriting constructors

第四种有时适用的技术是为 class 个成员设置默认值:

class Fribbler {
 public:
   fribble()  { i_have_been_fribbled_ = true; }
   is_fribbled() const { return i_have_been_fribbled_; }

 private:
   bool i_have_been_fribbled_ = false;
};

作为旁注,您不应在 C++ 中使用带有前导下划线的名称(又名 _)。这些名称保留供库实现或编译器使用。

  1. 使用 Inheriting constructors 重用 Rect 中的 ctors(即在您的 Square 中执行 using Rect::Rect
  2. 使用 Delegating constructors 从一个 ctor 调用另一个 ctor。
  3. 旁注,如果您计划以多态方式使用它们,您的基础 类 需要具有虚拟析构函数。

如果我没有正确理解你的问题,那么如果两个构造函数中字符串的初始值设定项相同,那么你可以使用委托构造函数

   explicit Square(double side) : 
   Rect(side, side), _owner("me"), _label("square") { }

   Square() : Square(1.0) {}

您也可以添加默认参数,例如

   explicit Square(double side, const char *owner = "me", const char *label = "square" ) : 
   Rect(side, side), _owner(owner), _label(label ) { }

   Square() : Square(1.0, "me", "unit square" ) {}

根据我的理解,C++中的构造函数重载与其函数重载非常相似,因此,重载的构造函数具有与 class 相同的名称但参数数量不同,并且取决于数量和传递的参数类型,调用特定的构造函数。

但是,您可以通过这种方式使它们类似

class Person
{
    int id;
    string name;

    public:
    //Empty Constructor
    Person(){
        this->id = 0;
        this->name = "default";
    }

    //Overloaded constructor with int parameter
    Person(int id){
        this->id = id;
    }

    //Overloaded constructor with a int parameter and a string
    Person(int id, string name){
        this->id =id;
        this->name = name;
    }

    void display(){
        cout << "Person Info: "<<" " << id << " " << name.c_str()<<"\n";
    }
};

你必须拼出 Rect 因为你可以继承多个 类。

您可以将两个 Square 构造函数简化为一个:

class Square : public Rect 
{
public:
   Square(double side = 1.0) : 
   Rect(side, side), _owner("me"), _label("square") { }

  // ...

}

如果你认真对待你的初始化值,你也可以这样做:

class Square : public Rect 
{
public:
   Square(double side = 1.0) : Rect(side, side) {}

private:
    std::string _owner = "me";
    std::string _label = "square";

}

唯一剩下的区别是 C++ 构造函数语法与 C# CTor 中的准赋值("quasi" 因为 CTors 中的赋值与其他地方的语义有些不同)

这种区别有一些优点:在左大括号处,所有成员和基 类 都已完全构建。 之所以重要有两个原因:

首先,C++ 明确了构造与赋值。 C# 隐藏了 "turning garbled memory into a valid object" 的更多方面。 (N.B。 性能 的许多优点可能在当今的桌面编译器的范围内。)

其次,由于 C++ 中存在确定性构造/破坏,因此初始化的顺序和依赖性很重要,例如,如果这些元素之间存在依赖性,或者如果任何初始化失败并引发异常。

(N.B。初始化的顺序取决于声明的顺序,而不是 CTor 初始化的顺序。)

给它一个星期,很快就会感觉自然了。