полиморфные ссылки на C ++ - PullRequest
       14

полиморфные ссылки на C ++

20 голосов
/ 25 августа 2011

Мне было интересно как можно сделать полиморфизм со ссылками, в отличие от указателей.

Для пояснения см. Следующий минимальный пример:

class A;

class B {
  public:
    A& a; ///////////////// <- #1
    B();
    void doStuff();
};

class A {
  public:
    virtual void doSmth() = 0;
};
void B::doStuff() {
  a.doSmth();
}

class A1 : public A {
  public:
    void doSmth() {
    }
};

B::B() : a(
    *        ////////////// <- #2
      (new A1)  /////////// <- #3
     ) {
}

Этокомпилируется и работает, но самым важным моментом здесь является то, что a в строке #1 является ссылкой, поэтому, чтобы иметь возможность использовать его полиморфно (это фактическое слово?), как показано в строке #3 Мне нужно «преобразовать указатель в ссылку», разыменовав его.

Мне кажется, что это немного странно, и мне было интересно, есть ли лучшее (в смысле cleaner ) путь.Это только я?

Обоснование

Было бы замечательно, если бы мне вообще не понадобился new, но при объявлении (!) B я понятия не имею, каксоздайте экземпляр A1 (!), так как A является предварительным объявлением - A1 реализован в том же модуле компиляции, что и B.Тем не менее, есть ли реальная потребность в динамическом распределении памяти в этом случае?Как бы вы это сделали?

Извините за слегка двоякий вопрос.

Редактировать

Примечание: B огромен (и я не могу сделать из него шаблонный класс)и выйдет из области действия именно тогда, когда программа завершит работу - a мала и заставляет два больших модуля общаться друг с другом, это будет необходимо до тех пор, пока существует экземпляр B (существует только один).

Edit 2

Я только что понял, что, поскольку и A, и B фактически являются одиночными, я могу просто создать static экземпляр A1 в единице компиляцииB, избегая динамического выделения памяти (даже если было два B с, они могли бы легко использовать один и тот же экземпляр A).Честно говоря, я не опубликовал это как ответ, но приму ответ, который побудил меня придумать это решение .

Ответы [ 7 ]

34 голосов
/ 25 августа 2011

В этом нет ничего странного.Полиморфизм работает как для указателей , так и ссылок:

struct Base { };
struct Derived : Base;

void foo(Base &);

int main() {
  Derived x;
  foo(x);    // fine
}

Вы связываете это с другой проблемой, а именно с созданием ссылки на динамический объект:

T * pt = new T;
T & rt = *pt;

T & x = *new T;  // same effect

Обратите внимание, что обычно очень плохой стиль для отслеживания динамического объекта только по ссылке, потому что единственный способ удалить его - через delete &x;, и очень трудно понять, что x нуждается в очистке.

Существуют две непосредственные альтернативы для вашего дизайна: 1) сделать a объектом-членом в B или 2) сделать a a shared_ptr<A> или unique_ptr<A> и изменить инициализатор на a(new A1).Все зависит от того, нужно ли вам на самом деле полиморфное поведение, т.е. если у вас есть другие конструкторы для B, которые назначают другой производный класс для a, отличный от A1.

3 голосов
/ 25 августа 2011

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

3 голосов
/ 25 августа 2011

Тем не менее, существует ли реальная потребность в динамическом распределении памяти в этом случай

Нет. Просто сначала определите A1, а затем сделайте его нормальным членом B.

Полиморфизм прекрасно работает как с ссылками, так и с указателями.

1 голос
/ 25 августа 2011

Эмм, этого не достаточно?

#include <iostream>

struct A;

struct B
{
  B(A& a);

  void foo();

  A& _a;
};

struct A
{
  virtual void foo() =0;
};

struct A1 : public A
{
  virtual void foo() { std::cout << "A1::foo" << std::endl; }
};

B::B(A& a) : _a(a) {}
void B::foo() { _a.foo(); }


int main(void)
{ 
  A1 a;  // instance of A1
  B b(a); // construct B with it

  b.foo();
}
0 голосов
/ 29 октября 2014

Я понимаю, что это действительно старая запись, но есть еще одна опция для обработки ссылок на динамически размещаемые объекты. Вы можете назначить ссылку на динамически размещенный объект. Ниже приведен некоторый фиктивный код, чтобы дать вам представление о том, как это работает.

struct A
{
  int b;
  virtual void print();
  A(int val):b(val) {}
};

struct A_child:public A
{
  A_child(int val):A(val) {}
  void print();
};

void A:print()
{
cout<<"parent\n";
}

void A_child:print()
{
cout<<"child\n";
}

struct test_ref
{
A *& ref;
test_ref(A * ptr) : ref(ptr)
}

int main()
{

  test_ref parent(new A(12));
  parent.ref->print();

  test_ref child(new A_child(15));
  child.ref->print();
} 

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

Я также вполне уверен, что динамическое выделение указателя при инициализации класса, в котором указатель хранится как указатель ссылки, вероятно, приведет к утечке памяти, если вы не можете удалить указатель ссылки.

0 голосов
/ 25 августа 2011

Нетрудно представить, почему ссылки могут работать полиморфно, как указатели (не говоря уже о том, что ссылки часто так или иначе реализуются как указатели).Вот краткий пример:

class Base { 
public:
    virtual void something() { }
};

class Derived : public Base {
public:
    void something() { }
};

Base& foo() {
    static Derived d;
    return d;
}

foo().something(); // calls Derived's something

Кроме того, почему вы выделяете динамическую память для ссылки?Вы, вероятно, не должны использовать ссылку в этом случае вообще.Кроме того, написание классов со ссылочными членами эффективно предотвращает назначение (как я слышал, кто-то сказал, что это хорошо).

0 голосов
/ 25 августа 2011

Тем не менее, существует ли реальная потребность в динамическом выделении памяти в этом случае?

Либо динамическое выделение памяти, либо вставка ссылки в c-код B.

...