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。
对于基本类型(例如 int
、double
、原始指针)default-initialisation 表示如果对象是全局对象则零初始化,否则表示“保持未初始化”以便访问它的价值给出了未定义的行为。所以,如果这样的成员是未初始化的,你唯一能做的就是给它赋值。执行任何访问其原始(未初始化)值(递增、向其添加值、乘法、复制等)的操作都会产生未定义的行为。
即使是基本的搜索也会出现各种解释,为什么(通常)建议避免引入未定义的行为。
对于struct
/class
类型,默认初始化意味着调用一个不接受任何参数的构造函数。如果该成员没有这样的构造函数,则有必要显式初始化该成员(list-initialise 它或调用它实际具有的构造函数)。然后推理是递归的(取决于每个成员有什么成员)。
实际上,如果在构造函数中保留 class 成员未初始化(与 default-constructed 不同)有意义,您可能需要问为什么 class 有那个成员根本。如果您想将成员的初始化延迟到构造对象之后,您需要质疑为什么不能延迟创建 class.
的实例
我有一些非常复杂的对象。它们包含其他对象的成员变量。我理解复制构造函数级联的美妙之处,以至于默认的复制构造函数通常可以工作。但是,最常破坏默认复制构造函数的情况(该对象包含一些成员变量,这些成员变量是指向其其他成员变量的指针)仍然适用于我构建的很多内容。这是我的一个对象、它的构造函数和我编写的复制构造函数的示例:
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。
对于基本类型(例如 int
、double
、原始指针)default-initialisation 表示如果对象是全局对象则零初始化,否则表示“保持未初始化”以便访问它的价值给出了未定义的行为。所以,如果这样的成员是未初始化的,你唯一能做的就是给它赋值。执行任何访问其原始(未初始化)值(递增、向其添加值、乘法、复制等)的操作都会产生未定义的行为。
即使是基本的搜索也会出现各种解释,为什么(通常)建议避免引入未定义的行为。
对于struct
/class
类型,默认初始化意味着调用一个不接受任何参数的构造函数。如果该成员没有这样的构造函数,则有必要显式初始化该成员(list-initialise 它或调用它实际具有的构造函数)。然后推理是递归的(取决于每个成员有什么成员)。
实际上,如果在构造函数中保留 class 成员未初始化(与 default-constructed 不同)有意义,您可能需要问为什么 class 有那个成员根本。如果您想将成员的初始化延迟到构造对象之后,您需要质疑为什么不能延迟创建 class.
的实例