C ++ Ковариация и ссылки - PullRequest
       18

C ++ Ковариация и ссылки

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

Допустим, у меня есть абстрактный базовый класс с чистым виртуальным, который возвращает дорогой объект.Поскольку это дорогой объект, я должен вернуть ссылку на него.

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

Я подумал, что мог бы просто использовать ковариацию, потому что принцип подстановки Лискова был бы счастлив, но, конечно, Obj неподтип Obj&, поэтому возникают ошибки компиляции.

class abc
{
public:
    virtual BigObj& obj() = 0;
};

class derived : public abc
{
public:
    ...
    virtual BigObj obj() { return obj_; }

private:
    BigObj obj_;
};

Результат:

conflicting return type specified for ‘virtual BigObj derived::obj()’

Есть ли более элегантное решение, чем просто выбор наименее худшего?

Ответы [ 5 ]

4 голосов
/ 29 августа 2011

Одним из решений является создание класса интеллектуальных указателей для управления BigObj* s:

class BigObjPtr {
public:
    BigObjPtr(bool del, BigObj* ptr) : del(del), ptr(ptr) { }

    BigObj* operator->() {
        return ptr;
    }

    virtual ~BigObjPtr() {
        if (del) delete ptr;
    }

private:
    BigObj* ptr;
    bool del;
};

Затем измените ваши классы, чтобы они возвращали один из них, и установите del bool на то, хотите ли вы, чтобы BigObjPtr уничтожил свой указатель, когда он выходит из области видимости:

class abc
{
public:
    virtual BigObjPtr obj() = 0;
};

class derived : public abc
{
public:
    ...
    BigObjPtr obj() { return BigObjPtr(false, &obj_); }

private:
    BigObj obj_;
};

class otherderived : public abc
{
public:
    ...
    BigObjPtr obj() { return BigObjPtr(true, new BigObj); }
};

Вам, конечно, нужно управлять копированием BigObjPtr с и т. Д., Но я оставлю это вам.

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

Вы должны переосмыслить свои предположения, интерфейс функций должен быть определен в терминах семантики.Итак, главный вопрос: Какова семантика функции?

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

Обратите внимание:

expensive function() {
   expensive result;
   return result;
}
expensive x = function();

Может быть оптимизирован компилятором в один объект expensive (он может избежать копирования из result в возвращаемый объект и удалить копию из возвращенного объекта в x).

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

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

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

2 голосов
/ 29 августа 2011

Вернуть вместо shared_ptr<BigObj>. Один класс может хранить свою собственную копию, а другой может создавать ее по требованию.

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

У вас есть два варианта:

  1. Как вы заметили, вы можете выбрать наименее худшее. То есть, выберите BigObj или BigObj& для обоих производных классов.

  2. Вы можете добавить новые методы к производным классам, которые имеют соответствующие возвращаемые типы. например, BigObj& obj_by_ref() и BigObj obj_by_val().

Причина, по которой вы не можете использовать оба способа, заключается в том, что у вас может быть указатель на abc напрямую. Он задает BigObj&, поэтому независимо от того, какой класс обеспечивает реализацию, он должен возвращать BigObj&, потому что именно этого ожидает сайт вызова. Если некорректно работающий подкласс вернул BigObj напрямую, это могло бы вызвать хаос, когда компилятор попытался использовать его как ссылку!

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

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

...