Конкретный класс не строится - PullRequest
1 голос
/ 25 сентября 2019

У меня есть иерархия классов, подобная этой:

class Base {
public:
    virtual bool foo() const = 0;
    static Base& getHeadInstance();
};

class Concrete: public Base {
public:
    explicit Concrete(Base& f): fwd(f) {}

    // ... other member functions elided
    bool foo() const override { return /* some calculation */ && fwd.foo(); }
private:
    Base& fwd;
};

, так что я могу построить серию таких примеров:

Concrete c1(Base::getHeadInstance());
Concrete c2(c1);
Concrete c3(c2);

, чтобы c3 мог принимать решения ивозможно, отложить до c2, что, в свою очередь, может отложить до c1 и т. д., как шаблон Chain of Responsibility.

Проблема в том, что c2 и c3 построены неправильно, и их fwd участник всегда ссылается на Base::getHeadInstance().

Что здесь происходит не так и как это исправить?

Обновление:

Этоне имеет значения, что возвращает статический член.Притворимся, что возвращает экземпляр этого:

class Head: public Base {
public:
    Head() = default;
private:
    bool foo() const override { return true; }
};
Base& Base::getHeadInstance(){ static Head head; return head; }

Ответы [ 4 ]

5 голосов
/ 25 сентября 2019

У меня была такая же проблема раньше.Это сводится к разрешению перегрузки вашего пользовательского конструктора Derived(Base&) против неявно определенного конструктора копирования Derived(const Derived&);, где неявный конструктор копирования просто выигрывает.Удаление этого не исправляет это, оно все еще участвует в разрешении перегрузки (но оно предотвращает молчание неправильной вещи).

Вот сокращенный пример:

struct Base
{
    virtual ~Base();
};

struct Derived : Base
{
    Derived(Base&);
    Derived(const Derived&); // Implicitly or explicitly declared in any case.
};

Derived getDerived();

void test()
{
    Derived d1 = getDerived();
    Derived d2(d1); // copies
}

https://godbolt.org/z/aoJFlC

Есть несколько способов заставить код делать то, что вы хотите, так, как вы его написали (см. Другие ответы), но я хотел бы отметить, что вы должны быть особенно внимательны, чтобы сохранить следующего читателявашего кода из той же путаницы у вас было.Приведение к Base&, измененная семантика конструктора копирования или что-то вроде использования Derived(Base*); вызовет вопросы у будущих читателей.Вы можете попытаться решить это с помощью документации, но есть вероятность, что кто-то упустит это и запутается.Я бы предложил сделать намерение настолько явным и видимым, насколько это возможно, например, так:

enum class ConstructFromBase { Tag };

struct Derived : Base
{
    Derived(Base&, ConstructFromBase);
    Derived(const Derived&) = delete;
};

Derived getDerived();

void test()
{
    Derived d1 = getDerived();
    Derived d2(d1, ConstructFromBase::Tag);
}

https://godbolt.org/z/QfeuAM

Это должно сообщать намерение очень четко и практически ничего не стоит.(Есть много других способов написать и назвать такой тег, мой, вероятно, не самый канонический ...)

4 голосов
/ 25 сентября 2019

У вас есть неявный конструктор копирования, от которого вам нужно избавиться:

Concrete(const Concrete& c) = delete;

, и вам придется разыграть c1 и c2

Concrete c1(Base::getHeadInstance());
Concrete c2((Base&)c1);
Concrete c3((Base&)c2);

ВашДругой вариант - создать шаблон конструктора:

template<typename T>
Concrete(T& f): fwd(f) {}

, и тогда ваш оригинальный код будет работать (Yay!).

Concrete c1(Base::getHeadInstance());
Concrete c2(c1);
Concrete c3(c2);
3 голосов
/ 25 сентября 2019

Вам просто нужно предоставить свой собственный конструктор копирования, который делает то, что вы хотите.Добавление

Concrete(Concrete& c): fwd(c) {}

сделает

Concrete c1(Base::getHeadInstance());
Concrete c2(c1);
Concrete c3(c2);

иметь c1.fwd == Base::getHeadInstance(), c2.fwd == c1 и c3.fwd == c2

1 голос
/ 25 сентября 2019

Другое возможное решение - избегать использования ссылок ;даже если я признаю, что это не изоморфно.

class Concrete : public Base {
 public:
  explicit Concrete(Base* f) : fwd(*f) {
    // possibly you want to assert precondition? assert(f)?
  }

 private:
  Base& fwd;  // I would prefer Base* here...
};

Таким образом, вы не рискуете вызвать неявный конструктор копирования из Concrete;без переопределения o удалить что-либо.

Concrete c1(&Base::getHeadInstance());
Concrete c2(&c1);
Concrete c3(&c2);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...