真实的例子显然要长得多,但这总结了我的问题:
class Object
{
int mInt1,mInt2;
Object::Object();
Object::Object(int param1);
Object::Object(int param1, int param2);
};
Object::Object(){}
Object::Object(int param1):mInt1(param1){}
Object::Object(int param1, int param2):mInt1(param1),mInt1(param2){}
然后在主要:
if (type1){
Object instance(param1);
}
else{
Object instance(param1,param2);
}
// do stuff with instance
糟糕!这不起作用,实例超出了后续程序的范围。
Object instance;
if (type1){
instance = Object(param1);
}
else{
instance = Object(param1,param2);
}
// do stuff with instance
但是现在我遇到了麻烦,因为我没有定义复制构造函数。我真的不想写一个拷贝构造函数,因为我的实际类有几十个成员,其中许多是非基本类型,可能需要更多的工作来复制。
具体来说,我正在
main.cpp: error: use of deleted function ‘Object& Object::operator=(Object&&)’
instance = Object(param1);
^
note: ‘Object& Object::operator=(Object&&)’ is implicitly deleted because the default definition would be ill-formed:
答案 0 :(得分:6)
处理不可复制对象的通用方法是将其抛入unique_ptr(或auto_ptr,具体取决于您的编译器)。
std::unique_ptr<Object> instance;
if (type1) {
instance.reset(new Object(i));
}
else {
instance.reset(new Object(i, j));
}
在这里使用原始指针确实不安全,因为一旦你开始处理异常或任何有趣的代码路径,就会担心泄漏。相信我,在100%的情况下,如果你将它放在unique_ptr 中,你将会有更少的工作和代码行来处理。
最佳解决方案是重新设计Object的构造函数,因为规避不可复制性可能会使对象处于非法状态。通常,如果编译器认为有必要,您希望保留不可复制性。但是,我们在此处没有详细说明这种解决方案。
答案 1 :(得分:5)
如果您不想动态分配,则可以使用Initialize函数:
class Object
{
int mInt1,mInt2;
Object::Object();
Object::Initialize();
Object::Initialize(int param1);
Object::Initialize(int param1, int param2);
};
Object::Object(){Initialize();} //call the empty Initialize for nice coding...:)
Object::Initialize(){ }
Object::Initialize(int param1){ mInt1(param1); }
Object::Initialize(int param1, int param2){ mInt1(param1);mInt1(param2);}
然后您可以使用initialize来选择类型。
Object instance;
if (type1){
instance.Initialize(param1);
}
else{
instance.Initialize(param1,param2);
}
答案 2 :(得分:3)
这是一个低级(ish)解决方案,可以满足您的需求。我将由您决定是否使用它是个好主意:
#include <type_traits>
template <class T>
class MultiInitialiser
{
T *object;
std::aligned_storage<T> storage;
public:
MultiInitialiser() : object(nullptr)
{}
template <class... Arg>
void initialise(Arg &&... arg)
{
if (object)
throw "Double init error";
object = new (&storage) T(std::forward<Arg>(arg)...);
}
operator T& ()
{ return *object; }
operator const T& () const
{ return *object; }
~MultiInitialiser()
{
if (object)
object->~T();
}
};
以上的复制/移动操作留作读者的练习; - )
然后将使用这样的类:
MultiInitialiser<Object> instance;
if (type1){
instance.initialise(param1);
}
else{
instance.initialise(param1,param2);
}
除了转化为T
之外,您还可以让课程operator*
和operator->
返回包含的对象,类似于boost::optional
。
答案 3 :(得分:3)
有几个选项可以让您在保留自动存储的同时执行此操作,您应该使用哪个选项取决于类型Object
的语义。
如果您的类型与问题中给出的类型类似,您可以选择将其缩小为POD类型;基本上,删除所有用户提供的构造函数,并为所有内容提供相同的访问说明符:
struct Object {
int mInt1, mInt2;
};
然后,您的初始化模式可能如下所示(使用placement new):
Object o; // Default-initialized, a no-op
if (condition)
new (&o) Object {i};
else
new (&o) Object {i, j};
一般来说,由于移动语义,默认初始化然后分配,你的典型值语义类型将完美地运行:
std::vector <foo> v;
if (condition)
v = std::vector <foo> (42);
else
v = std::vector <foo> {bar, baz, quux};
但是,通常,您仍然会在默认构造函数中工作,因为某些类型&#39;默认构造的对象(例如,std::vector
)具有明确定义的状态。如果你想为任意预定义类型避免这项工作,你可能想要使用std::optional
(截至撰写本文时尚未标准化):
std::optional <big_but_flat> b;
if (condition)
b.emplace (i);
else
b.emplace (i, j);
std::optional
您可能会反对std::optional
与此相关的开销太大,我会留给您和您的测量结果以确定是否属于这种情况。无论如何,我们可以获得我们的行为而不用担心这种开销 - 但如果你没有真正执行初始化,那么鼻子恶魔可能会怜悯。我们会使用union
来获取我们想要的内容:
// At function scope
union store_v {
std::vector <int> v;
store_v () {}
~store_v () { v.~vector <int> (); }
} sv;
if (condition)
new (&sv.v) std::vector <int> (42);
else
new (&sv.v) std::vector <int> {49, 343, 2401};
这可能会有所改善。例如,我们可以将存储设为模板:
template <typename T>
union store {
T t;
store () {}
~store () { t.~T (); }
};
// At function scope
store <std::vector <int>> sv;
if (condition)
new (&sv.t) std::vector <int> (42);
else
new (&sv.t) std::vector <int> {49, 343, 2401};
我们可以给自己一个参考:
template <typename T>
union store {
T t;
store () {}
~store () { t.~T (); }
};
// At function scope
store <std::vector <int>> sv;
auto&& v = sv.t; // Deduce const, for what that's worth
if (condition)
new (&v) std::vector <int> (42);
else
new (&v) std::vector <int> {49, 343, 2401};
为了避免名称冲突并处理C ++的...有趣的声明语法,我们甚至可以定义一些宏来清理代码(将实现作为练习留给读取器):
template <typename T>
union store {
T t;
store () {}
~store () { t.~T (); }
};
// At function scope
DECL_UNINIT (std::vector <int>, v);
if (condition)
INIT (v, (42));
else
INIT (v, {49, 343, 2401});
答案 4 :(得分:2)
您可以使用指向对象的指针并通过new运算符实例化它:
Object * instance;
if (type1){
instance = new Object(param1);
}
else{
instance = new Object(param1,param2);
}
答案 5 :(得分:2)
您正在使用名为 copy elision 的内容。
这表明编译器 MAY 优化代码并避免在这种情况下使用复制构造函数。 但它不必,也可能使用复制构造函数。
正确的方法(不受编译器的异想天开)将使用指针:
Object* instance;
if (type1){
instance = new Object(param1);
}
else{
instance = new Object(param1,param2);
}
答案 6 :(得分:1)
你可以写一个移动作业。根据您的数据成员的样子,您可能会忘记编写部分或全部内容,参见Move constructor with memcpy
那就是说,我假设您需要一整套构造函数/析构函数,包括复制和赋值;如果你想把它放在容器里,分配它等等,那将永远是必要的。否则班级不需要任何这些,你只需根据情况初始化它所需的部分,当你完成时,你手动取消初始化。
答案 7 :(得分:1)
在你的单参数构造函数的版本中,mInt2
成员只是被忽略(没有被初始化),所以我假设你不会对那个成员进行任何计算{} {1}}是type1
(虽然我不知道你如何在不存储false
的情况下这样做。)
那么,为什么不改变dessign呢?让te构造函数以type1
,int param1
和int param2
作为参数,并在内部选择如何构建自己:
type1
然后在class Object
{
int mInt1,mInt2;
Object::Object() :
mInt1(0), // don't forget to initialize your values!
mInt2(0)
{}
// Object::Object(int param1); no more 1-parameter ctor.
Object::Object(int param1, int param2, type type1) :
mInt1(param1),
mInt2(type1 ? param2 : 0) // assuming that 0 isn't a valid value for mInt2
{}
};
:
main
我猜它看起来有点整洁。
答案 8 :(得分:0)
我想我找到了一种方法来做而不需要在堆上分配对象,使用lambda。 基本上,这个想法是将结构与用法分开。 (以下假设您修复了Object的定义以便编译)
auto work = []( Object& object ){
// here do the work on the object
};
// now create the object and apply the work in the process:
if (type1){
Object instance(param1);
work( instance );
}
else{
Object instance(param1,param2);
work( instance );
}
这里没有涉及副本,但是无论构造对象是什么,仍然应用相同的代码,而不必声明外部函数(作为lambda是本地函数)。 从内存的角度来看,总会只有一个实例对象,因此无论路径如何,分配的堆栈内存总是相同的大小。
显然,如果实例必须超出整个功能的范围,这不起作用。如果是这种情况,那么你真的需要使用智能指针在堆上分配它。