2016-01-13 2 views
7

У меня есть метод, который принимает параметр, который является ссылкой на базовый класс, и я епдиеий вызовы тела метода путем обертывания реализации методы в queue<function<void()>>C++ лямбды: как избежать нарезок ссылки, если захвачено значением

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

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

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

Помните, что метод должен быть реентерабельным, но не асинхронным или параллельным.

Это пример того, что я имею в виду (опуская очереди):

struct BaseObj { 
    virtual ~BaseObj() = default; 
}; 

struct DerivedObj : public BaseObj { 

}; 

void someMethod(BaseObj& obj) { 

    // obj is of type BaseObj: 
    std::cout << "\nobj type:" << typeid(obj).name(); 

    auto refLambda = [&] { 
     // captured obj is of type DerivedObj: 
     std::cout << "\nrefLambda::obj type:" << typeid(obj).name(); 
    }; 

    auto valLambda = [=] { 
     // captured obj is of type BaseObj: 
     // presumably because it was copied by value, which sliced it. 
     std::cout << "\nvalLambda::obj type:" << typeid(obj).name(); 
    }; 

    refLambda(); 
    valLambda(); 
} 

Выходной сигнал при вызове метода, как так:

DerivedObj obj{}; 
someMethod(obj); 

Is:

obj type:10DerivedObj 
refLambda::obj type:10DerivedObj 
valLambda::obj type:7BaseObj 

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

  1. Передача кучи выделенного объекта из вызывающего кода.
  2. захват по ссылке в лямбда.
  3. убедитесь, что вы не изменяете оригинал в вызывающем коде.
  4. , наконец, удаляет кучу obj после возврата метода.

Как это:

DerivedObj* obj = new DerivedObj(); 
    someMethod(*obj); 
    delete obj; 

Но я надеялся, чтобы иметь возможность просто передать ссылку из стека вызова кода и будет хорошо, даже если внутри someMethod что-то происходит, что вызывает еще один вызов someMethod.

Любые идеи?

Один из подходов, о котором я думал, но я не уверен, как это сделать, внутри `someMethod ', перемещая параметр в кучу, выполняя лямбду и затем, наконец, удаляя ее (поскольку вызывающий на самом деле не использует после вызова этого метода). Но не уверен, что это действительно взломанный (я только думал об этом, потому что это немного похоже на то, что делают блоки Objective-C).

обновление:

Это решение, которое я до сих пор:

void Object::broadcast(Event& event) { 
    auto frozenEvent = event.heapClone(); 

    auto dispatchBlock = [=]() { 
     for (auto receiver : receivers) { 
      receiver.take(event); 
     } 

     delete frozenEvent; 
     _private->eventQueue.pop(); 
     if (!_private->eventQueue.empty()) { 
      _private->eventQueue.front()(); 
     } 
    }; 

    _private->eventQueue.push(dispatchBlock); 
    if (_private->eventQueue.size() == 1) { 
     _private->eventQueue.front()(); 
    } 
} 

Да, я знаю, я использую сырые указатели ... (eeeeevil ....: р), но по крайней мере я могу сохранить подпись метода с параметром ref.

Метод клонирования вдоль линий этого:

template <class T> 
struct ConcreteEvent : public Event { 
    virtual Event* heapClone() { 
     return new T(*(T*)this); 
    } 

    // .... more stuff. 
}; 
+0

как насчет make 'someMethod' метод шаблона? Вы не можете копировать объект без его типа. или просто передайте 'shared_ptr' около –

+0

@BryanChen умный указатель будет работать, но я пытался его избежать. То же, что и методы шаблонов. – SaldaVonSchwartz

+0

Обратите внимание на [Основные рекомендации C++] (https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md). Я считаю, что код C++, помеченный C++ 11 или C++ 14, должен серьезно рассмотреть возможность использования умного указателя вместо голого нового и удаления. –

ответ

0

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

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

+0

Похоже на это. Благодарю. На данный момент я применил метод копирования кучи (как показано в обновлении моего вопроса). – SaldaVonSchwartz

+0

@SaldaVonSchwartz, если бы вы знали тип статически в вызывающем, вы могли бы сделать функцию шаблоном. Но в остальном метод «клонировать» - единственный способ. Вы можете попробовать использовать [Boost.Type Erasure] (http://www.boost.org/doc/libs/1_60_0/doc/html/boost_typeerasure.html) для обработки клонирования (он может обрабатывать клонирование объектов без добавления базового класса для них, но это довольно много материала, чтобы учиться). –

+0

@JanHudec пытается держаться подальше от Boost, так как я делаю это «с нуля» специально. Но спасибо. – SaldaVonSchwartz

-2

Используйте указатель как someMethod аргумент вместо:

void someMethod(BaseObj* obj) { 
    std::cout << "\nobj type:" << typeid(*obj).name(); 
    auto refLambda = [&] { 
     std::cout << "\nrefLambda::obj type:" << typeid(*obj).name(); 
    }; 

    auto valLambda = [=] { 
     std::cout << "\nvalLambda::obj type:" << typeid(*obj).name(); 
    }; 

    refLambda(); 
    valLambda(); 
} 

int main() { 
    DerivedObj obj; 
    someMethod(&obj); 
} 

Испытано в VS2013 было бы напечатать:

obj type:struct DerivedObj 
refLambda::obj type:struct DerivedObj 
valLambda::obj type:struct DerivedObj 
+3

Если вы захватили указатель по значению, вы получите тот же результат, как если бы вы захватили объект по ссылке: вы до сих пор не получаете копию объекта. Так что это не отвечает на вопрос в моем оппионе. –

+0

Ах да. Я пропустил тот факт, что OP хочет получить копию для каждого элемента в очереди. –

+0

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

Смежные вопросы