C ++ Полиморфизм: допускается ли неоднозначный тип члена? - PullRequest
0 голосов
/ 14 мая 2019

Теперь мне нужен член неоднозначного типа в классе.Здесь я говорю неоднозначный , это означает, что член может быть двух или более типов одного и того же базового класса.См. Код ниже.

class Base {
protected:
    int m_int;
    Base(int i) : m_int(i) {}
};

class Derived1 : Base {...};
class Derived2 : Base {...};

class AnotherClass {
private:
    Base m_member;  // <- this member
public:
    AnotherClass(int selection) {
        // if (selection)
        //     m_member = Derived1(...);
        // else
        //     m_member = Derived2(...);
    }
};

Я не знаю точно, как инициализировать этот элемент.Есть предложения по этому поводу?Может быть, указатели / ссылки?

Ответы [ 4 ]

1 голос
/ 14 мая 2019
static const Base& Base::getDerivedClass(int derivedID) {
    if(derivedID == 1) return Derived1();
    .... 
} 
AnotherClass(int derivedID) : m_member(Base::getDerivedClass(derivedID))
1 голос
/ 14 мая 2019

Вы должны либо использовать std::variant, либо указатель, либо обертку со стертым типом (РЕДАКТИРОВАТЬ: в конце я добавил реализацию обертки со стертым универсальным типом). Вот решение указателя:

class AnotherClass {
private:
    std::unique_ptr<Base> m_member;
public:
    AnotherClass(int selection) {
        if (selection)
            m_member = std::make_unique<Derived1>(...);
        else
            m_member = std::make_unique<Derived2>(...);
    }
};

К сожалению, AnotherClass будет только подвижным, не копируемым. Если вы хотите иметь возможность скопировать его, вы должны предоставить конструктор копирования. Конструктор копирования должен иметь возможность клонировать элемент:

class Base {
protected:
    int m_int;
    Base(int i) : m_int(i) {}
public:
    // For covariance, return a raw pointer instead.
    virtual std::unique_ptr<Base> clone() const = 0;
};

class Derived1 : public Base {
public:
   std::unique_ptr<Base> clone() const override { 
      return std::make_unique<Derived1>();
   }
...
};
class Derived2 : public Base { .. same ... };

class AnotherClass {
public:
   AnotherClass(const AnotherClass & other) : m_member(other.m_member->clone()) {}
   AnotherClass(AnotherClass && other) = default;
   .... same for assignment ...


EDIT:

Альтернативой является использование обертки со стертым типом, которая заботится о безобразном материале:

#include <type_traits>
#include <memory>

template <class BaseType>
struct AbstractWrapper {
public:
    virtual ~AbstractWrapper() = default;
    virtual std::unique_ptr<AbstractWrapper<BaseType>> clone() = 0; 
    virtual BaseType * get() = 0;
};

template <class BaseType, class T>
struct ConcreteWrapper : public AbstractWrapper<BaseType> {
    static_assert(std::is_base_of<BaseType, T>::value);
    T data;
    template <class U, class = std::enable_if_t<(!std::is_same_v<ConcreteWrapper, std::decay_t<U>>)>>
    ConcreteWrapper(U && value) : data(std::forward<U>(value)) {}
    std::unique_ptr<AbstractWrapper<BaseType>> clone() override
    {
        return std::make_unique<ConcreteWrapper>(*this);
    }
    BaseType * get() override { return &data; } 
};
template <class BaseType>
class TypeErasedWrapper
{
public:    
    TypeErasedWrapper(const TypeErasedWrapper & other) 
        : container(other.container->clone()) 
    {}
    TypeErasedWrapper(TypeErasedWrapper && other) = default;
    TypeErasedWrapper() {}


    template <class U, class = std::enable_if_t<std::is_base_of_v<BaseType, std::decay_t<U>>>>
    TypeErasedWrapper(U && concrete) {
        container = std::make_unique<ConcreteWrapper<BaseType, std::decay_t<U>>>(std::forward<U>(concrete));
    }
    template <class U>
    TypeErasedWrapper& operator = (U && concrete) {
        *this = TypeErasedWrapper(std::forward<U>(concrete));
        return *this;
    }

    TypeErasedWrapper& operator=(const TypeErasedWrapper & other) {
        container = other.container->clone();
        return *this;
    }

    TypeErasedWrapper& operator = (TypeErasedWrapper && other) = default;

    BaseType * operator->() { return container->get();}
    const BaseType * operator->() const { return container->get();}
    BaseType * get() { return container ? container->get() : nullptr;}
    const BaseType * get() const { return container? container->get() : nullptr;}



private:
   std::unique_ptr<AbstractWrapper<BaseType>> container;
};

Использование:

class AnotherClass {
private:
    TypeErasedWrapper<Base> m_member;
public:
    AnotherClass(int selection) {
        if (selection)
            m_member = Derived1(...);
        else
            m_member = Derived2(...);
    }
    int getValue() const {
       // Assuming getBaseValue() is a method of Base
       return m_member->getBaseValue();
    }
};
0 голосов
/ 14 мая 2019

Как предлагается в комментариях, правильным способом для достижения вашей цели будет использование std::unique_ptr<Base>.

class AnotherClass {
private:
    std::unique_ptr<Base> m_ptr;
public:
    AnotherClass (int selection) {
        if (selection) m_ptr = std::make_unique<Derived1>();
        else m_ptr = std::make_unique<Derived2>();
    }
    //...
}

Вы можете использовать размещение новых , , но это плохая идея .

union DerivedAny {
    char m_mem1[sizeof(Derived1)] alignas(Derived1);
    char m_mem2[sizeof(Derived2)] alignas(Derived2);
};

class AnotherClass {
private:
    char m_mem[sizeof(DerivedAny)] alignas(DerivedAny);
    Base * base () { return reinterpret_cast<Base *>(m_mem); }
public:
    AnotherClass (int selection) {
        if (selection) new (m_mem) Derived1;
        else new (m_mem) Derived2;
    }
    //...
};

Поскольку m_mem был создан, AnotherClass нужен деструктор, чтобы правильно его очистить.

    ~AnotherClass () { base()->~Base(); }

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

Так что такой подход чреват подводными камнями.

0 голосов
/ 14 мая 2019

Самый ленивый способ инициализации - добавить параметр в конструктор.

private:
    Base& m_member;
public:
    AnotherClass(Base& ref) : m_member(ref) {}

Обратите внимание, что это не будет способ добавления члена.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...