Это не работает:
struct Type {
virtual bool func(const std::string& val) const noexcept = 0;
}
// in main
optional<Type> = some_function_returning_optional_type();
и завершается с сообщением об ошибке:
error: cannot declare field 'std::experimental::fundamentals_v1::_Optional_base<Type, false>::<anonymous union>::_M_payload' to be of abstract type 'Type'
Изменение Type
иметь не-чистую функцию работает, но не подходит в этом случае, потому что не может быть экземпляра Type
в моем коде должны существовать только классы, которые наследуют его.
std::optional<T>
сохраняет его значение на месте — поэтому ему нужно знать размер T
работать правильно, и T
должен быть конкретным типом, который может быть создан. Вы можете думать о std::optional<T>
как:
template <typename T>
struct optional
{
std::aligned_storage_t<sizeof(T), alignof(T)> _data;
bool _set;
};
Абстрактный тип представляет интерфейс — полиморфизм и некоторая косвенность необходима для работы с абстрактными типами. std::optional
не имеет никакого косвенного влияния по дизайну.
Ваше предложение по желанию, конечно, будет работать, но мне было бы обидно писать
x.value()->do_something();
и я буду обеспокоен тем, что пользователи могут сделать что-то глупое:
x.value().reset(); // now what?
Мы можем достичь полиморфизма с неполиморфным интерфейсом, используя оболочку.
Вот один из способов:
#include <optional>
#include <iostream>
// the Foo interface/base class
struct Foo
{
virtual ~Foo() = default;
virtual Foo* clone() const { return new Foo(*this); }
virtual void do_something() {
std::cout << "something Fooey\n";
}
};
// a service for managing Foo and classes derived from Foo
struct FooService
{
template<class Arg>
Foo* clone(Arg&& arg)
{
using d_type = std::decay_t<Arg>;
return new d_type(std::forward<Arg>(arg));
}
template<class Arg>
Foo* clone(Foo* arg)
{
return arg->clone();
}
Foo* release(Foo*& other) noexcept
{
auto tmp = other;
other = nullptr;
return tmp;
}
};
// implement the Foo interface in terms of a pimpl
template<class Holder>
struct BasicFoo
{
decltype(auto) do_something() {
return get().do_something();
}private:
Foo& get() noexcept { return static_cast<Holder*>(this)->get_impl(); }
Foo const& get() const noexcept { return static_cast<Holder const*>(this)->get_impl(); }
};// a type for holding anything derived from a Foo
// can be initialised by anything Foo-like and handles copy/move correctly
struct FooHolder : BasicFoo<FooHolder>
{
template
<
class Arg,
std::enable_if_t
<
std::is_base_of_v<Foo, std::decay_t<Arg>>
>* = nullptr
>
FooHolder(Arg&& arg)
: service_()
, ptr_(service_.clone(std::forward<Arg>(arg)))
{}
FooHolder(FooHolder const& other)
: service_()
, ptr_(other.ptr_->clone())
{
}
FooHolder(FooHolder && other) noexcept
: service_()
, ptr_(service_.release(other.ptr_))
{
}
FooHolder& operator=(FooHolder const& other)
{
auto tmp = other;
std::swap(ptr_, tmp.ptr_);
return *this;
}
FooHolder& operator=(FooHolder && other) noexcept
{
auto tmp = std::move(other);
std::swap(ptr_, tmp.ptr_);
return *this;
}
~FooHolder()
{
delete ptr_;
}
Foo& get_impl() noexcept { return *ptr_; }
Foo const& get_impl() const noexcept { return *ptr_; }
FooService service_;
Foo* ptr_;
};
// now we can supply as many overrides of Foo as we like
struct Bar : Foo
{
virtual Foo* clone() const { return FooService().clone(*this); }
virtual void do_something() {
std::cout << "something Barey\n";
}
};
int main()
{
std::optional<FooHolder> opt;
// note that we're initialising cleanly
opt = Bar {};
// and we don't expose the pointer so the user can't
// destroy the pimpl accidentally
opt.value().do_something();
}