Выбор конструктора для не подлежащего копированию объекта

Предположим, что у меня есть класс, не подлежащий копированию, с несколькими конструкторами, такими как

class Foo: boost::noncopyable
{
  public:
    Foo(std::string s) {...};  // construct one way        
    Foo(int i) {...};  // construct another way
 }

Теперь я хочу построить объект и выбрать, какой конструктор использовать во время выполнения:

Я мог бы сделать это с помощью указателей вроде: -

boost::shared_ptr<Foo> f;

if (condition)
  f.reset(new Foo(myString));
else
  f.reset(new Foo(myInteger));

// common code follows
f->doSomethingComplicated(...);

Но это кажется грязным и медленным. Есть ли простой способ выбрать конструктор для объекта, не прибегая к динамическому распределению?


Дополнительная информация: класс Foo, приведенный выше, просто иллюстрирует проблему. Фактический класс - это Windows Gdiplus::Bitmap - http://msdn.microsoft.com/ан-ГБ/library/окна/настольные/ms534420 (v = vs .85).aspx

Ответ 1

Вы можете сделать это с С++ 11 в стеке, без размещения новых и без возможности создания/назначения copy/move. Обратите внимание:

auto factory = [&]() -> Foo 
{ 
  if (condition) {
    return { myString };
  } else {
    return { myInteger };
  }
};
Foo&& foo = factory();
foo.doSomethingComplicated();

Функтор и экземпляр Foo будут жить счастливо в стеке, никаких ассигнований (за исключением, например, копирования строки в конструкторе Foo). Foo получит свой деструктор, когда он выходит из сферы действия. Win.

При использовании единой инициализации для построения возвращаемого значения не выполняется операция копирования/перемещения. Обратите внимание, что возвращение myString (неявное преобразование) или Foo(myString) заставляет компилятор проверять, может ли объект копироваться/перемещаться, даже если такая копия/перемещение может быть отменено.

Изменить: альтернативно, это можно сделать еще короче, но немного более "магическим":

Foo&& foo = [&]() -> Foo 
{ 
  if (condition) {
    return { myString };
  } else {
    return { myInteger };
  }
}();

Изменить: Pre-С++ 11, хакерское решение для Visual Studio:

VC, похоже, не проверяет, можно ли копировать объект при использовании неявных конструкторов преобразования для возврата значения из функции. Так что это действительно, даже несмотря на то, что оно соответствует стандарту:

Foo make_foo(bool condition, const std::string&s, int i)
{
    if ( condition) {
        return s;
    } else {
        return i;
    }
}

Тогда просто используйте его так:

Foo& f = make_foo(condition, myString, myInteger);

Обратите внимание, что это еще один взломанный VC, так как согласно стандартным временным не может быть привязан к ссылке на изменяемый объект, т.е. его нужно будет изменить const Foo&, что здесь будет довольно ограничено.

Не говори, что ты должен справиться с этим так, но это возможно.

Ответ 3

Я думаю, что лучший вариант, который соответствует вашим требованиям (а не динамически распределенный, не копируемый, pre-С++ 11), - это использовать placement new.

Для краткого примера см. здесь.

boost::optional также может быть приемлемым для вас (он в основном просто делает это для вас внутренне, а также отслеживает, был ли объект инициализирован). Конструкция на месте немного беспорядочна, но для optional imo.

Ответ 4

Держите его маленьким и простым, я бы сказал. Если это очень локальная проблема, почему бы просто не повторить вызов?

if (condition)
{
  Foo f(myString);
  f.doSomethingComplicated();
}
else
{
  Foo f(myInt);
  f.doSomethingComplicated();
}

Если это не представляется возможным, заверните указатель Foo (или умный указатель) в новый класс.

class FooWrapper // copyable
{
private:
    boost::shared_ptr<Foo> m_foo;
public:
    FooWrapper(std::string const &s) : m_foo(new Foo(s)) {}
    FooWrapper(int i) : m_foo(new Foo(i)) {}
    void doSomethingComplicated() { m_foo->doSomethingComplicated(); }
};

FooWrapper foo = condition ? FooWrapper(myString) : FooWrapper(myInt);
foo.doSomethingComplicated();

Ответ 5

С помощью С++ 11 вы можете:

Foo&& f = condition ? Foo{myInteger} : Foo{myString};
f.doSomethingComplicated();

Используя ссылку на r-значение, вы избегаете константы и продолжаете продлить время жизни временного.

Ответ 6

Если вам нужно только создать временный объект, я думаю, вы можете связать его с ссылкой на const, используя трехмерное выражение:

const Foo &f(condition ? Foo(myString) : Foo(myInteger));

f.doSomethingComplicated(...);