C++ Super-FAQ 『Constructor』

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ Super-FAQ 『Constructor』相关的知识,希望对你有一定的参考价值。

  • 什么是构造函数
Constructors build objects from dust.
They turn a pile of arbitrary bits into a living object.
 
  • List x, List x()与List x(Bar())
List x,声明一个类型为List名为x的对象;
List x(),声明一个名为x的函数,返回List类型;【LLVM提示『Empty parenthess interpreted as a function declartion』】
List x(Bar()),声明一个名为x的函数,含有一个类型为Bar的参数,返回类型List。
若想通过临时对象Bar构造一个名为x的List对象,应当写成List x{Bar()},{}被称为uniform initialization(since C++11)。
 
  • 在构造函数中能否调用本类型的其他构造函数
C++11增加的constructor chainging使得该操作可行。【具体行为还需补充】
C++11之前该操作不可行。因为,新调用的构造函数会在原地创建临时对象,该语句执行完毕后立即销毁。
 
默认构造函数的调用
默认构造函数是无需任何参数就能被调用的构造函数。
尽量使用vector而不是array构造对象数组,因为前者可以指定使用哪个构造函数,而后者只能使用默认构造函数。
1 vector<Fred> a(10, Fred(5,7));
2 Fred b[10];
 
例外:当需要 “explicit initialization of arrays” 这种语法时,使用array更合适。
Fred a[10] = {
    Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7),  // The 10 Fred objects are
    Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7)   // initialized using Fred(5,7)
  };

 

  • 构造函数initialization list的调用顺序

Immediate base classes (left to right), then member objects (top to bottom).

注意:

若member objects包含嵌套,则必须将被嵌套的对象声明在前。

initialize list按照member objects声明的顺序初始化。

若一个成员变量的初始化使用另一成员变量的值时,必须明确表明他们的声明顺序。

 

  • 在构造函数中访问this指针

在构造函数体({})内,可以访问this指针,因为此时所有的数据成员已经准备完毕。

但在构造函数中无法访问派生类override的虚函数,因为此时this还不知道它是一个派生类对象。

 

  • Named Constructor Idiom

这是一种能够提供更直观、更安全地构造类的技术。

若一种类型含有许多构造函数,仅仅通过参数类型不易区分他们,这时就可采用『Named Constructor Idiom』。

比如,点坐标既能使用笛卡尔坐标系也能使用极坐标系,而他们的构造参数一致。这时可将他们的普通构造函数声明为私有,然后通过Named Constructor供用户构造访问。

Point p1 = Point::rectangular(5.7, 1.2);   // Obviously rectangular
Point p2 = Point::polar(5.7, 1.2);         // Obviously polar

若使用者需确保对象必须通过new创建,也可使用该技术。

 

  • return-by-value是否意味着额外开销

不一定。

因为在某些用法下,(商业级)编译器会自动优化,将局部对象的指针而不是副本返回。

class Foo;
Foo Method(){
  return Foo(...);
}

void Func() {
  Foo x = Method();  //会优化
  Foo y;
  y = Method();    //不一定优化
}

 

  • 类的static成员声明

除声明static成员外,代码中还必须包含该对象的定义,否则会导致链接错误『undefined external』。

若声明static const成员,则定义时不能通过=对其初始化。

 

  • static initialization order fiasco

这是一种极难被发现的细微错误,该错误一般发生在main()被调用之前。

eg: 在A.cpp中定义static X x,在B.cpp中定义static Y y,且y的初始化需要用到x,若在y初始化前x未初始化,则会出现该错误。

一般采用Construct On First Use Idiom避免上述问题,即将静态对象包裹在函数中。
// File x.cpp
#include "Fred.h"
//方式一
Fred x;
//方式二
Fred& x()
{
  static Fred* ans = new Fred();
  return *ans;
}
 
// File y.cpp
#include "Barney.h"
Barney y;
 
// File Barney.cpp
#include "Barney.h"
//方式一
Barney::Barney()
{
  // ...
  x.goBowling();
  // ...
}
//方式二
Barney::Barney()
{
  // ...
  x().goBowling();
  // ...
}
这种用法的缺点在于该静态对象永远不会被销毁。

eg: 一种通过前置声明在对象初始化前访问该对象的例子。

int f();  // forward declaration
int g();  // forward declaration

int x = f();
int y = g();

int f()
{
  std::cout << "using ‘y‘ (which is " << y << ")\n";
  return 3*y + 7;
}

int g()
{
  std::cout << "initializing ‘y‘\n";
  return 5;

}
 
  • Named Parameter Idiom
C++中函数参数类型一般被称为『positional parameters』。而Ada语言含有一种被称为『named parameters』的参数类型。这种类型对于含有许多参数,且大部分参数都使用默认值的函数十分有用。
C++通常有两种方法解决该问题:
将这些参数整合为一个字符串,传递给函数后解释。
将所有的布尔参数整合为一个bit-map,传递给函数后解释。
 
Named Parameter Idiom的解决方法是:将函数参数该为一种新类型的函数,该函数以引用的形式范围this。
File f = OpenFile("foo.txt")
           .readonly()
           .createIfNotExist()
           .appendWhenWriting()
           .blockSize(1024)
           .unbuffered()
           .exclusiveAccess();

class File {
public:
  File(const OpenFile& params);
  // ...
};
此处的OpenFile就是新创建的类,通过他的成员函数可以对File的属性参数进行设置。 

  • 关键字explicit

该关键字可用于构造函数和转换操作符的修饰。目的是用户使用这些函数时必须显示调用,避免隐式类型转换的问题。

 

参考资料:https://isocpp.org/wiki/faq/ctors

以上是关于C++ Super-FAQ 『Constructor』的主要内容,如果未能解决你的问题,请参考以下文章

C++ Super-FAQ 『Operator Overloading』

C++ Super-FAQ 『Classes and Objects』

Uncaught TypeError: Cannot read properties of undefined (reading ‘Constructo

java 动态编译

TypeScript 类型兼容

Spring 级联属性