C++ 复制构造函数:我必须拼出初始化列表中的所有成员变量吗?

C++ Copy Constructors: must I spell out all member variables in the initializer list?

我有一些非常复杂的对象。它们包含其他对象的成员变量。我理解复制构造函数级联的美妙之处,以至于默认的复制构造函数通常可以工作。但是,最常破坏默认复制构造函数的情况(该对象包含一些成员变量,这些成员变量是指向其其他成员变量的指针)仍然适用于我构建的很多内容。这是我的一个对象、它的构造函数和我编写的复制构造函数的示例:

struct PhaseSpace {
  PhaseSpace();

private:
  std::string file_name;              ///< Name of the file from which these coordinates (and
                                      ///<   perhaps velocities) derived.  Empty string indicates
                                      ///<   no file.
  int atom_count;                     ///< The number of atoms in the system
  UnitCellType unit_cell;             ///< The type of unit cell
  Hybrid<double> x_coordinates;       ///< Cartesian X coordinates of all particles
  Hybrid<double> y_coordinates;       ///< Cartesian Y coordinates of all particles
  Hybrid<double> z_coordinates;       ///< Cartesian Z coordinates of all particles
  Hybrid<double> box_space_transform; ///< Matrix to transform coordinates into box space (3 x 3)
  Hybrid<double> inverse_transform;   ///< Matrix to transform coordinates into real space (3 x 3)
  Hybrid<double> box_dimensions;      ///< Three lengths and three angles defining the box (lengths
                                      ///<   are given in Angstroms, angles in radians)
  Hybrid<double> x_velocities;        ///< Cartesian X velocities of all particles
  Hybrid<double> y_velocities;        ///< Cartesian Y velocities of all particles
  Hybrid<double> z_velocities;        ///< Cartesian Z velocities of all particles
  Hybrid<double> x_forces;            ///< Cartesian X forces acting on all particles
  Hybrid<double> y_forces;            ///< Cartesian Y forces acting on all particles
  Hybrid<double> z_forces;            ///< Cartesian Z forces acting on all particles
  Hybrid<double> x_prior_coordinates; ///< Previous step Cartesian X coordinates of all particles
  Hybrid<double> y_prior_coordinates; ///< Previous step Cartesian Y coordinates of all particles
  Hybrid<double> z_prior_coordinates; ///< Previous step Cartesian Z coordinates of all particles

  /// All of the above Hybrid objects are pointers into this single large array, segmented to hold
  /// each type of information with zero-padding to accommodate the HPC warp size.
  Hybrid<double> storage;

  /// \brief Allocate space for the object, based on a known number of atoms
  void allocate();
};

//-------------------------------------------------------------------------------------------------
PhaseSpace::PhaseSpace() :
    file_name{std::string("")},
    atom_count{0},
    unit_cell{UnitCellType::NONE},
    x_coordinates{HybridKind::POINTER, "x_coordinates"},
    y_coordinates{HybridKind::POINTER, "y_coordinates"},
    z_coordinates{HybridKind::POINTER, "z_coordinates"},
    box_space_transform{HybridKind::POINTER, "box_transform"},
    inverse_transform{HybridKind::POINTER, "inv_transform"},
    box_dimensions{HybridKind::POINTER, "box_dimensions"},
    x_velocities{HybridKind::POINTER, "x_velocities"},
    y_velocities{HybridKind::POINTER, "y_velocities"},
    z_velocities{HybridKind::POINTER, "z_velocities"},
    x_forces{HybridKind::POINTER, "x_forces"},
    y_forces{HybridKind::POINTER, "y_forces"},
    z_forces{HybridKind::POINTER, "z_forces"},
    x_prior_coordinates{HybridKind::POINTER, "x_prior_coords"},
    y_prior_coordinates{HybridKind::POINTER, "y_prior_coords"},
    z_prior_coordinates{HybridKind::POINTER, "z_prior_coords"},
    storage{HybridKind::ARRAY, "phase_space_data"}
{}

//-------------------------------------------------------------------------------------------------
PhaseSpace::PhaseSpace(const PhaseSpace &original) :
    file_name{original.file_name},
    atom_count{original.atom_count},
    unit_cell{original.unit_cell},
    x_coordinates{original.x_coordinates},
    y_coordinates{original.y_coordinates},
    z_coordinates{original.z_coordinates},
    box_space_transform{original.box_space_transform},
    inverse_transform{original.inverse_transform},
    box_dimensions{original.box_dimensions},
    x_velocities{original.x_velocities},
    y_velocities{original.y_velocities},
    z_velocities{original.z_velocities},
    x_forces{original.x_forces},
    y_forces{original.y_forces},
    z_forces{original.z_forces},
    x_prior_coordinates{original.x_prior_coordinates},
    y_prior_coordinates{original.y_prior_coordinates},
    z_prior_coordinates{original.z_prior_coordinates},
    storage{original.storage}
{
  // Set the POINTER-kind Hybrids in the new object appropriately.  Even the resize() operation
  // inherent to "allocate" will pass by with little more than a check that the length of the data
  // storage array is already what it should be.
  allocate();
}

编辑:这里是 allocate() 成员函数,如果它有助于解释任何事情...... storage 数组已经分配到与原始数组相同的长度,并由混合对象提供了深层副本类型自己的复制赋值构造函数,剩下的就是将此 PhaseSpace 对象自己的指针类混合对象设置到数组类混合对象中的适当位置 storage.

//-------------------------------------------------------------------------------------------------
void PhaseSpace::allocate() {
  const int padded_atom_count  = roundUp(atom_count, warp_size_int);
  const int padded_matrix_size = roundUp(9, warp_size_int);
  storage.resize((15 * padded_atom_count) + (3 * padded_matrix_size));
  x_coordinates.setPointer(&storage,                            0, atom_count);
  y_coordinates.setPointer(&storage,            padded_atom_count, atom_count);
  z_coordinates.setPointer(&storage,        2 * padded_atom_count, atom_count);
  box_space_transform.setPointer(&storage,  3 * padded_atom_count, 9);
  inverse_transform.setPointer(&storage,   (3 * padded_atom_count) +      padded_matrix_size, 9);
  box_dimensions.setPointer(&storage,      (3 * padded_atom_count) + (2 * padded_matrix_size), 6);
  const int thus_far = (3 * padded_atom_count) + (3 * padded_matrix_size);
  x_velocities.setPointer(&storage,        thus_far,                           atom_count);
  y_velocities.setPointer(&storage,        thus_far +      padded_atom_count,  atom_count);
  z_velocities.setPointer(&storage,        thus_far + (2 * padded_atom_count), atom_count);
  x_forces.setPointer(&storage,            thus_far + (3 * padded_atom_count), atom_count);
  y_forces.setPointer(&storage,            thus_far + (4 * padded_atom_count), atom_count);
  z_forces.setPointer(&storage,            thus_far + (5 * padded_atom_count), atom_count);
  x_prior_coordinates.setPointer(&storage, thus_far + (6 * padded_atom_count), atom_count);
  y_prior_coordinates.setPointer(&storage, thus_far + (7 * padded_atom_count), atom_count);
  z_prior_coordinates.setPointer(&storage, thus_far + (8 * padded_atom_count), atom_count);
}

注释来自实际代码,尽管我(可能很明显)省略了原始结构定义中的大量细节。我的问题是复制构造函数是否需要如此详细的初始化列表,或者是否有更优雅(更不容易出错)shorthand 来制作这样的自定义复制构造函数。一旦我得到了这个问题的答案,我就有了一些具有相同情况的更大的对象。

干杯!

C++ Copy Constructors: must I spell out all member variables in the initializer list?

是的,如果您编写用户定义的复制构造函数,那么您必须为每个子对象编写一个初始化程序 - 除非您希望默认初始化它们,在这种情况下您不需要任何初始化程序 - 或者如果您可以使用默认成员初始化器。

the object contains some member variables which are pointers to its other member variables)

这种设计应该尽可能避免。这不仅迫使您定义自定义复制和移动赋值运算符和构造函数,而且通常效率低下。

但是,如果出于某种原因这是必要的 - 或者由于任何其他原因需要自定义特殊成员函数 - 您可以通过将正常复制的部分组合到一个单独的虚拟 class 来实现干净的代码。这样一来,用户定义的构造函数只有一个子对象需要初始化。

像这样:

struct just_data {
    // many members... details don't matter
    R just_data_1;
    S just_data_2;
    T just_data_3;
    U just_data_4;
    V just_data_5;
};

struct fancy_copying : just_data
{
    fancy_copying(const fancy_copying& other)
        : just_data(other.just_data)
    {
        do_the_fancy();
    }
};

嗯,不,在构造函数的初始化列表中列出 所有 成员并不是 essential。但是,如果你不这样做,你需要接受并以某种方式减轻后果。

一些成员的初始化是强制性的。例如,有必要在创建引用时对其进行初始化(以使其正确引用某物)。我假设您不会试图让这些成员未初始化。

您未在构造函数初始化列表中显式初始化的任何成员都将是 default-initialised。

对于基本类型(例如 intdouble、原始指针)default-initialisation 表示如果对象是全局对象则零初始化,否则表示“保持未初始化”以便访问它的价值给出了未定义的行为。所以,如果这样的成员是未初始化的,你唯一能做的就是给它赋值。执行任何访问其原始(未初始化)值(递增、向其添加值、乘法、复制等)的操作都会产生未定义的行为。

即使是基本的搜索也会出现各种解释,为什么(通常)建议避免引入未定义的行为。

对于struct/class类型,默认初始化意味着调用一个不接受任何参数的构造函数。如果该成员没有这样的构造函数,则有必要显式初始化该成员(list-initialise 它或调用它实际具有的构造函数)。然后推理是递归的(取决于每个成员有什么成员)。

实际上,如果在构造函数中保留 class 成员未初始化(与 default-constructed 不同)有意义,您可能需要问为什么 class 有那个成员根本。如果您想将成员的初始化延迟到构造对象之后,您需要质疑为什么不能延迟创建 class.

的实例