8

真实的例子显然要长得多,但这总结了我的问题:

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:
4

9 回答 9

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 的构造函数,因为规避不可复制性可能会使对象处于非法状态。通常,如果编译器认为有必要,您希望保留不可复制性。但是,我们这里没有详细信息来充实这样的解决方案。

于 2014-03-18T14:41:55.523 回答
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);}

然后您可以使用初始化来选择类型。

Object instance;
if (type1){
  instance.Initialize(param1);
}
else{
  instance.Initialize(param1,param2);
}
于 2014-03-18T13:01:25.257 回答
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做。

于 2014-03-18T13:16:55.070 回答
3

有几个选项可以让您在保留自动存储的同时执行此操作,您应该使用哪一个取决于 type 的语义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};

不过,您通常仍会在默认构造函数中工作,因为某些类型的默认构造对象(例如std::vector)具有明确定义的状态。如果您想避免对任意预定义类型进行这项工作,您可能希望使用std::optional(在撰写本文时实际上还不是标准的):

std::optional <big_but_flat> b;

if (condition)
    b.emplace (i);
else
    b.emplace (i, j);

没有std::optional

您可能会反对std::optional与它相关的过多开销,我将把它留给您和您的测量来决定是否是这种情况。无论如何,我们可以得到我们的行为而不用担心开销——但是如果你没有真正执行你的初始化,鼻恶魔可能会怜悯。我们将使用 aunion来获得我们想要的:

// 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});
于 2014-03-18T20:16:35.150 回答
2

您可以使用指向对象的指针并通过 new 运算符对其进行实例化:

Object * instance;
if (type1){
  instance = new Object(param1);
}
else{
  instance = new Object(param1,param2);
} 
于 2014-03-18T12:56:43.273 回答
2

您正在使用称为复制省略的东西。

这表明编译器可以优化代码并在这种情况下避免使用复制构造函数。但它不必,并且无论如何都可以使用复制构造函数。

正确的方法(不受编译器的异想天开)是使用指针:

Object* instance;
if (type1){
  instance = new Object(param1);
}
else{
  instance = new Object(param1,param2);
}
于 2014-03-18T12:57:11.887 回答
1

你可以写一个移动作业。根据您的数据成员的外观,您可能会通过记忆复制其中的部分或全部而侥幸,参见。使用 memcpy 移动构造函数

也就是说,我假设您需要一整套构造函数/析构函数,包括复制和赋值;如果您想将它放在容器中,分配它等,这将始终是必要的。否则该类不需要任何这些,您只需根据情况初始化它所需的部分,完成后,您手动取消初始化。

于 2014-03-18T13:27:06.203 回答
1

在您的单参数构造函数版本中,该mInt2成员只是被忽略(从未初始化),因此我假设您不会对该成员进行任何计算type1false尽管我不知道您在做什么)它没有存储type1)。

那么,为什么不直接改变设计呢?让构造函数接受int param1,int param2type1作为参数并在内部选择如何构建自己:

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

Object instance(param1, param2, type1);
// do stuff with instance

我想它看起来更整洁一些。

于 2014-03-18T15:55:22.113 回答
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 是本地函数)。从内存的角度来看,总是只有一个实例对象,因此无论路径如何,分配的堆栈内存总是相同的大小。

显然,如果实例必须超出整个函数的范围,这将不起作用。如果是这种情况,那么您确实需要最好使用智能指针在堆上分配它。

于 2014-03-22T00:14:24.130 回答