为什么不能像这样在C++中动态声明对象数组:

5
在我的C++项目中,有一个需要创建对象数组的类。在类的不同实例之间,数组的大小将会不同,因此我选择了使用数组。
如果我执行以下操作:
int numberOfPlayers; // This is determined at run time.
int *players; 

//In constructor
players= new int[numberOfPlayers]; // This works

但是如果我这样做:

Character *players;
players = new Character[numberOfPlayers]; // Compiler complains

编译器抱怨“没有匹配的构造函数来初始化Character”
我如何动态声明类型为"Character"的数组。
注意:Character与char无关。Character是我自己创建的类的名称。
编辑:Character没有默认构造函数,因为它需要传递多个参数,以便可以用正确的状态进行初始化。它唯一的构造函数需要传入多个参数。
编辑:我选择了一个动态创建的数组,而不是vector,因为我知道在实例的生命周期内,数组的大小将是恒定的,尽管在不同的实例之间,大小将不同。我认为这对于性能(内存/速度)来说是有意义的。

5
请注意,使用new创建数组是最不实用的方法之一。考虑使用std::vector - M.M
2
是的,使用 std::vector,你甚至不用担心这个默认构造函数的问题。请查看下面的答案。 - Drew Dormann
你写了Character类吗?有时缺少默认构造函数是故意的,并暗示着在某些构造参数可用之前,Character对象可能没有意义 - 你可以等到它们可用后再使用push_backemplace将元素添加到std::vector<Character>中,这通常比使用数组和new更容易正确使用。 - Tony Delroy
是的。我写了Character类,没有默认构造函数,因为对象在没有可用的构造参数(就像你说的那样)之前是没有意义的。我认为,由于数组的大小在类的生命周期内不会改变,动态分配数组比向量更好。 - Rahul Iyer
@John:为了创建一个对象数组,编译器需要调用类的构造函数。因为 new[] 操作符不允许传递任何参数到构造函数中,所以编译器只能使用默认构造函数。 - Matthieu M.
6个回答

9

“正确”的方法是使用std::vector。它是一种快速、安全、更健壮的替代可怕的new

std::vector<Character> vec;
vec.push_back(Character(params));
vec.push_back(Character(other_params));

如果你预先知道大小,你可以通过使用std::vector::reserve来避免重新分配的开销。
std::vector<Character> vec;
vec.reserve(50);
vec.push_back(Character(params));
vec.push_back(Character(other_params));

std::vector的开销几乎可以忽略不计。

现在,你不能按照你的方式做的原因是,默认情况下new使用默认构造函数,但它不存在。


1
比我的解释更好。好的例子。+1 - Drew Dormann
2
emplace_back 避免了临时对象的创建。例如:vec.emplace_back(params); - Rapptz

4
问题在于您的类型Character没有定义以下形式的默认构造函数:
Character::Character()
{
   // etc.
}

如果没有默认构造函数是不是不合理?Character需要多个参数来初始化它到正确的状态。 - Rahul Iyer
2
@John:那就不要给它一个。但不要用这种方式创建动态数组。改用std::vector。实际上,即使你有默认构造函数,也应该使用std::vector - Benjamin Lindley

3

您的类型需要一个默认构造函数。与C的malloc不同,operator new在分配时为您构造实例。因此,它需要一个无参数(默认)构造函数,因为它没有提供传递参数的方法。所以...

class Character
{
public:
    Character(){}
};

我认为对于Character来说,一个默认构造函数并没有意义,因为在将其初始化为“正确”状态之前需要多个信息片段。你认为在这种情况下使用malloc更有意义吗?还有更好的替代方法吗? - Rahul Iyer
@John:使用非POD类型的malloc会导致未定义行为,所以不行。你需要一个默认构造函数。我理解你想确保所有必需的数据都存在,但你只需要提供默认值即可。只需使用std::vector,这才是你应该处理这个问题的方式。话虽如此,你也应该学习C++中的内存分配机制。 - Ed S.

3

字符没有默认构造函数,因为它需要传递多个参数,这样它才能使用正确的状态进行初始化。唯一的构造函数需要传递多个参数。

因此,使用数组是错误的类型,因为数组始终会对其成员进行默认构造。

请使用:

std::vector<Character> players;

大小可以根据您的要求变化,当每个角色被构建时,您可以使用call players.push_back(character)


2
简短的回答是,你不能这样做,因为标准不允许。没有技术上的原因不允许它 - 只是没有被允许。
一些编译器(例如gcc)多年来一直支持它作为C++的扩展。各种编译器也长期支持C中的它,所以C99将其标准化,因此现在所有(相当新的)C编译器都支持它。
曾经有一个提案添加了一个类似数组的类,在创建时确定大小,并且在那之后保持不变,但委员会决定不接受它。这只剩下std :: array,它需要在编译时确定大小,以及std :: vector,其大小可以在运行时动态变化。
然而,公平地说,如果您在创建向量时知道其大小,则可以在创建时指定大小。虽然它仍然能够自我调整大小,但大多数功能都在resize和push_back中。如果您只是不使用它们,则与本机数组相比,使用std :: vector的开销通常非常小,因此您不太可能从其他技术中获得显着收益(除非在相当模糊的情况下)。
1. 至少在我看来,这是正确的决定 - 虽然我可以看到这个想法的基本推理,但我认为该提案存在足够的缺陷,我们最好不要使用它。

1
因为分配一个Character数组意味着该数组将包含一定数量的Character实例。当你分配数组时,必须以某种方式初始化每个包含的实例,并且需要默认构造函数。
你必须声明Character::Character() { }以让编译器调用它。如果你无法提供默认构造函数,则应考虑使用Character**,这样你可以根据需要进行初始化,例如:
Character **array = new Character*[amount];
array[0] = new Character(...);

请注意,这需要删除每个实例,因此您需要使用delete[] array的替代方法。
for (int i = 0; i < amount; ++i)
  delete array[i];
delete [] array;

另外一种方法是放弃使用数组,改用 std::vector

vector<Character> character;
character.push_back(Character(...));

这也将减轻您自己管理内存的需求。

由于我知道数组的大小将在类的生命周期内保持不变,那么 vector 会使用不必要的内存 / 变慢吗? - Rahul Iyer
@John:在证明性能无关紧要之前,性能都是无关紧要的。你不应该担心编写高效的代码,首先你应该担心编写正确和可维护的代码,然后才考虑性能。对于你的目的而言,性能差异可以忽略不计。 - Jack
@John 首先你需要分配指针数组和 Characters(也就是 (sizeof(Character)+sizeof(Character*)) * amount),此外每个 Character 对象都需要单独分配(这就是为什么它很慢的原因)。 - milleniumbug
@John 这种方式动态分配的数组与向量完全相同。如果你想要非常挑剔,可以这样做:std::vector<Character> characters; characters.reserve(numberOfPlayers);,它将在向量中至少提供numberOfPlayers个元素。然后,您可以使用emplace_back来就地构造对象。求求你了,请不要手动运行new和delete数组。那是一定会在以后给你带来麻烦的。 - user3010322
2
最后,对你来说是个-1:双指针用于懒初始化?认真的吗? - user3010322
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接