如何声明其类没有默认构造函数的对象数组?
Posted
技术标签:
【中文标题】如何声明其类没有默认构造函数的对象数组?【英文标题】:How do I declare an array of objects whose class has no default constructor? 【发布时间】:2010-02-26 17:36:18 【问题描述】:如果一个类只有一个构造函数,一个参数,如何声明一个数组?我知道在这种情况下推荐使用向量。例如,如果我有一个类
class Foo
public:
Foo(int i)
如何声明包含 10000 个 Foo 对象的数组或向量?
【问题讨论】:
请不要从答案中得出结论,如果您不立即对其进行初始化,则无法引用该数组。你总是可以做extern Foo foo[100];
然后已经引用了数组,只要你稍后定义它并且然后它需要所有的初始化器:)
Dagnammit,我输入了关于 extern
的相同评论,但我认为我应该在发布之前检查它是否真的有效,而你打败了我。我认为你甚至不需要定义它,只要你不引用它。
为什么要声明数组而不是向量?
我刚刚对 C++0x GCC 感到很开心,并为std::array
提出了一种机制:codepad.org/O4bP8KO9 :) 我怀疑这是最接近的机制 - 但至少它是一个原生的真实数组。 @David,因为vector
是动态分配的,所以我认为这太过分了。在带有键盘代码的 C++0x 中,我们甚至可以constexpr
所有涉及的函数(如果我们避免使用引用参数),并受益于静态初始化以避免初始化顺序失败,我想。跨度>
请注意,声明数组不会有问题。 定义很难。 (见这里:***.com/questions/1410563)
【参考方案1】:
对于数组,您必须在定义数组的位置为数组的每个元素提供一个初始值设定项。
对于向量,您可以为向量的每个成员提供要复制的实例。
例如
std::vector<Foo> thousand_foos(1000, Foo(42));
【讨论】:
对于普通数组,我只是说你没有有这样做,这一切......似乎具有误导性。还有其他实用的替代方案。另外,这里显示的向量方法不允许使用不同的参数调用构造函数。 @Dan Moulding:但是你不能定义一个没有默认构造函数的类类型数组而不提供初始化器。我支持这一说法。这个问题要求一个数组或一个向量,并且没有就所需的值提供任何指导,所以即使有其他可能性,我仍然认为我的答案不正确或没有帮助。 很公平。我想如果你在谈论数组 types 那是真的。我更多地考虑将数组视为连续的对象序列,在这种情况下......还有其他选择。我会收回我的反对票,但令我惊讶的是,SO 不会让我投票,因为我的投票现在“太旧了”——嗯??? @Dan,请考虑meta.stackexchange.com/questions/30470/… @litb:感谢您的指点。我以前没有听说过这个东西(我从不去元)。似乎最好不允许对您也回答过的问题的答案进行投票,不是吗?考虑到潜在的 COI 和所有(当然,我在这里对此感到内疚,但不是出于任何邪恶、战术或其他自私的原因——我只是诚实地认为答案具有误导性,并且得到的支持超过了应得的) .【参考方案2】:其实只要使用初始化列表就可以了,比如
Foo foos[4] = Foo(0),Foo(1),Foo(2),Foo(3) ;
但是对于 10000 个对象,这是绝对不切实际的。我什至不确定您是否足够疯狂地尝试编译器是否会接受这么大的初始化列表。
【讨论】:
你可以少写一点(如果构造函数不明确的话):Foo foos[] = 0, 1, 2, 3 ;
在你写的时候,这是不切实际的。然而,skydoor 询问了数组或向量。
@David Rodríguez - dribeas 哦,当然!我知道那件事。但是为了这个例子,我更喜欢明确。 @sbi 我明白你的意思。也许我应该说考虑到使用矢量是多么不切实际。
这是在没有动态内存的嵌入式系统上运行的方法。
只是想指出,如果 Foo ctor 采用多个参数,例如 Foo(int x, int y),那么您可能必须嵌套另一层统一初始化:@987654323 @【参考方案3】:
sbi 对普通数组有最佳答案,但没有给出示例。所以……
你应该使用placement new:
char *place = new char [sizeof(Foo) * 10000];
Foo *fooArray = reinterpret_cast<Foo *>(place);
for (unsigned int i = 0; i < 10000; ++i)
new (fooArray + i) Foo(i); // Call non-default constructor
请记住,当使用placement new时,你负责调用对象的析构函数——编译器不会为你做这件事:
// In some cleanup code somewhere ...
for (unsigned int i = 0; i < 10000; ++i)
fooArray[i].~Foo();
// Don't forget to delete the "place"
delete [] reinterpret_cast<char *>(fooArray);
这是您唯一一次看到对析构函数的合法显式调用。
注意:第一个版本在删除“地点”时有一个微妙的错误。将“地方”转换回新的相同类型很重要。换句话说,fooArray
在删除时必须被强制转换回char *
。有关说明,请参阅下面的 cmets。
【讨论】:
我没有反对,因为该方法没有任何根本性的错误,但您可以使用std::vector<Foo>
、reserve
和 push_back
获得相同的效果,而且更容易使其健壮且异常安全。
是的。当我写这篇文章时,我显然忽略了问题末尾的“或向量”,并且完全专注于标题中总结的问题。既然如此,这很可能是唯一实用的方法。我当然不会说这比使用向量更好,但是如果您需要一个普通数组...
不需要手动调用析构函数。事实上,这是有害的,因为delete[] fooArray
在释放内存之前已经这样做了。另外,我会将前两行替换为Foo* fooArray = reinterpret_cast<Foo*>(operator new(10000 * sizeof(Foo)))
。
@FredOverflow:哎呀。你发现了一个错误。在删除它之前,我忘记将它转换回数组指针。尝试delete [] fooArray
会导致编译器尝试销毁sizeof(foo) * 10000
Foo 对象,这显然是不正确的。始终删除相同类型的新对象非常重要。我更新了sizeof(Foo) * 10000
字符,我必须删除sizeof(Foo) * 10000
字符。当然,这就是您确实需要显式调用 Foo 析构函数的原因。固定。
@sbi: delete [] place
也可以,只要place
仍在范围内。我的例子没有说得很清楚,但我假设清理代码在不同的上下文中,因此,place
可能不再在范围内(你当然可以保存“地方”,但是你必须保存两个指针而不是一个)。【参考方案4】:
你需要做一个指向 Foo 的指针数组。
Foo* myArray[10000];
for (int i = 0; i < 10000; ++i)
myArray[i] = new Foo(i);
【讨论】:
在大多数情况下,这不是一个解决方案(即 Foo 对象需要是连续的)。这比在第一种情况下使用std::vector
更能改变场景。
同意。我完全错过了他最后一句话中的“矢量”一词。我认为他的“我知道推荐使用向量”是想说“我知道向量是正确的解决方案,但我还是想要一个数组”。【参考方案5】:
当你声明一个没有默认构造函数的对象时,你必须在声明中初始化它。
Foo a; // not allowed
Foo b(0); // OK
这种类型的数组也是如此:
Foo c[2]; // not allowed
Foo d[2] = 0, 1 ; // OK
Foo e[] = Foo(0), Foo(1), Foo(2) ; // also OK
在您的情况下,您可能会发现像这样初始化所有 10,000 个元素是不切实际的,因此您可能希望重新考虑该类是否真的不应该有默认构造函数。
【讨论】:
我不坚持,@Andrey。我只是不总是记得数组初始值设定项中是否发生隐式转换。编辑以显示两种语法。多参数构造函数需要显式语法,但对于单参数非显式构造函数是可选的。 但是对于给定的 10000 个对象,这并不是一个真正的选择,是吗? 它不是一个选项的唯一原因,@Sbi,是编译器的内部限制是否阻止它编译一个像具有 10,000 个元素的初始化器列表一样复杂的初始化器列表。如果允许,那么这是一种选择。这不是一个实用选项,但我已经在回答中指出了这一点。 我明白了。很抱歉我忽略了这一点。但是,我仍然觉得这并不能真正回答问题,因为那是关于数组 或 向量的。 有一个使用std::vector
的实用解决方案。【参考方案6】:
在没有默认构造函数的情况下定义类数组的唯一方法是立即对其进行初始化——这并不是一个拥有 10000 个对象的选项。
但是,您可以根据需要分配足够的原始内存,并使用放置new
在该内存中创建对象。但是如果你想这样做,最好使用std::vector
,它就是这样做的:
#include <iostream>
#include <vector>
struct foo
foo(int)
;
int main()
std::vector<foo> v;
v.resize(10000,foo(42));
std::cout << v.size() '\n';
return 0;
【讨论】:
为什么如果他们做同样的事情,最好使用std::vector
?是因为使用 STL 后代码大小增加了 50%? ;)
@Dan:更好,因为它避免了常见错误。 (而增加尺寸的说法只是简单的 FUD。)
普通 FUD?在我正在开发的一个嵌入式系统上,仅使用 one 向量实例使我的共享库大小从 ~100KiB 跃升至超过 1MiB。那不是FUD。而且,在这种情况下,这是我负担不起的增长。这是 GCC。
@sbi:嗯,这确实让我感到厌烦。 IME 软件人员倾向于拥有非常狭隘的软件世界观。大多数人认为 Linux/Windows/Mac 桌面应用程序和 Web 开发就是一切。事实证明,实际上有 一大群 人从事嵌入式工作。这不是利基市场。
@sbi:从桌面软件的角度来看,这不是一篇糟糕的文章。具有讽刺意味的是我并没有忘记;)有两件事:a)SO上的答案不仅适用于提出问题的人,而且(可能主要甚至)也适用于来寻找答案的人稍后。对于有这个问题的嵌入式开发人员来这里并找到仅适用于桌面环境的答案会有所帮助吗? b) 库开发人员最好考虑所有他们的代码可能有用的系统。保守可能很重要。 +1,顺便说一句:)【参考方案7】:
您必须使用聚合初始值设定项, 之间有 10000 个单独的初始值设定项
Foo array[10000] = 1, 2, 3, ..., 10000 ;
当然,指定 10000 个初始化器是不可能的,但你自己要求的。你想声明一个包含 10000 个对象的数组,没有默认构造函数。
【讨论】:
请注意,问题是关于 10000 个对象。这使得编译器需要吞下相当大的文件。【参考方案8】:class single
int data;
public:
single()
data = 0;
single(int i)
data = i;
;
// in main()
single* obj[10000];
for (unsigned int z = 0; z < 10000; z++)
obj[z] = new single(10);
【讨论】:
【参考方案9】:另一种选择可能是使用 boost::optional<Foo>
的数组:
boost::optional<Foo> foos[10]; // No construction takes place
// (similar to vector::reserve)
foos[i] = Foo(3); // Actual construction
需要注意的是,您必须使用指针语法访问元素:
bar(*foos[2]); // "bar" is a function taking a "Foo"
std::cout << foos[3]->baz(); // "baz" is a member of "Foo"
您还必须注意不要访问未初始化的元素。
另一个需要注意的是,这不是 Foo
数组的真正替代品,因为您无法将它传递给期望后者的函数。
【讨论】:
好主意,但需要Foo
有一个复制构造函数不是吗?
@Mark - 好点。但是你可以通过一个小技巧绕过这个限制:codepad.org/csde4mwT【参考方案10】:
在直接 C 中,使用 int foo[10000] = 1;
会将第一个数组项初始化为 1
并将数组的其余部分初始化为零。 C++ 不会自动初始化未指定的数组成员,还是需要默认构造函数?
【讨论】:
在 C++ 中,剩余的数组成员是值初始化的,在这种情况下确实需要一个默认构造函数。【参考方案11】:如果它对你的类有意义,你可以为你的构造函数参数提供一个默认值:
class Foo
public:
explicit Foo(int i = 0);
现在你有了一个默认的构造函数。 (“默认构造函数”是可以不带参数调用的构造函数:FAQ)
我还建议像上面那样明确构造你的构造函数。当您不想请求它们时,它将阻止您从int
s 获取Foo
s。
【讨论】:
【参考方案12】:试试这个。
Foo **ppInstances=0;
size_t total_instances = 10000;
for(int parent=0;parent < total_instances;parent++)
ppInstances[parent]=new Foo( parent );
ppInstances++;
for(int parent=0;parent < total_instances;parent++)
delete *ppInstances;
ppInstances--;
【讨论】:
【参考方案13】:正确的方法是使用std::aligned_storage
。当您想要访问某个项目时,您将不得不手动构造和销毁项目以及reintrepret_cast
。我建议您围绕storage_t
编写一个小型包装类来处理这个问题。有人提到使用boost::optional
,它在引擎盖下使用 bool 和 storage_t。这种方法为您节省了一个布尔值。
template<typename T>
using storage_t = typename std::aligned_storage<sizeof(T), alignof(T)>::type;
struct Foo;
size_t i = 55;
storage_t<Foo> items[1000]; // array of suitable storage for 1000 T's
new (reintrepret_cast<Foo*>(items + i)) Foo(42); // construct new Foo using placement new
*reintrepret_cast<Foo*>(items + i) = Foo(27); // assign Foo
reintrepret_cast<Foo*>(items + i)->~Foo() // call destructor
【讨论】:
以上是关于如何声明其类没有默认构造函数的对象数组?的主要内容,如果未能解决你的问题,请参考以下文章