Предположим, у меня есть не копируемый класс с несколькими конструкторами с таким
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/en-gb/library/windows/desktop/ms534420(v=vs.85).aspx
Вы можете сделать это с C ++ 11 в стеке, без размещения нового и без копирования / перемещения конструктора / назначения. Заметим:
auto factory = [&]() -> Foo
{
if (condition) {
return { myString };
} else {
return { myInteger };
}
};
Foo&& foo = factory();
foo.doSomethingComplicated();
Функтор и экземпляр Foo будут счастливо жить в стеке, выделения не производятся (кроме копирования строки в Foo
конструктор, наверное). Foo вызовет свой деструктор, когда выйдет из области видимости. Выиграть.
При использовании равномерной инициализации для создания возвращаемого значения, операция копирования / перемещения не выполняется. Обратите внимание, что возвращение myString
(неявное преобразование) или Foo(myString)
заставит компилятор проверить, является ли объект копируемым / перемещаемым, даже если такое копирование / перемещение может быть исключено.
Изменить: в качестве альтернативы, это может быть сделано еще короче, но немного более «волшебным»:
Foo&& foo = [&]() -> Foo
{
if (condition) {
return { myString };
} else {
return { myInteger };
}
}();
Изменить: Pre-C ++ 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&
что было бы весьма ограничивающим здесь.
Не сказать, что вы должны справиться с этим, но это возможно.
У вас есть недостаток в вашем дизайне. ‘Foo’ делегирует тип elision пользователю класса. Посмотрите на boost :: any (http://www.boost.org/doc/libs/1_55_0/doc/html/any.html)
Я думаю, что лучшим вариантом, отвечающим вашим требованиям (не динамически размещаемым, не копируемым, до C ++ 11), будет использование placement new
,
Увидеть Вот для краткого примера.
boost::optional
также может быть приемлемым вариантом для вас (в основном он делает это для вас внутренне, а также отслеживает, был ли объект инициализирован). Конструкция на месте немного грязная, хотя для optional
имо.
Держите это маленьким и простым, я бы сказал. Если это очень локальная проблема, почему бы просто не повторить звонок?
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();
Используя C ++ 11 вы можете сделать:
Foo&& f = condition ? Foo{myInteger} : Foo{myString};
f.doSomethingComplicated();
Используя ссылку на r-значение, вы избегаете постоянства и все же продлеваете время жизни временного.
Если вам нужно только создать временный объект, я думаю, что вы можете связать его с константной ссылкой, используя троичное выражение:
const Foo &f(condition ? Foo(myString) : Foo(myInteger));
f.doSomethingComplicated(...);