2013-08-02 3 views
5

Существуют различные способы возврата коллекции элементов из метода класса в C++.Компромиссы при возврате коллекции

Например, рассмотрим класс MessageSpy, который прослушивает все сообщения, отправленные по соединению. Клиент может получить доступ к информации о сообщениях несколькими способами.

  1. Const CollectionClass MessageSpy :: GetMessages()
  2. Итератор MessageSpy :: начать() итератор MessageSpy :: конец()
  3. недействительного MessageSpy :: GetMessages (OutputIterator)
  4. недействительного MessageSpy :: eachMessage (Functor)
  5. другие ...

Каждый подход имеет свои компромиссные решения. Например: для подхода 1 требуется скопировать всю коллекцию, которая является дорогостоящей для больших коллекций. В то время как подход 2 делает класс похожим на коллекцию, которая неприемлема для представления ...

Поскольку я всегда стараюсь выбрать наиболее подходящий подход, мне интересно, что вы считаете компромиссами/расходами при рассмотрении этих подходов ?

+0

Yikes, комментарии к этому вопросу исчезли! Был один комментарий с ссылкой на «обязательный документ». Может ли кто-нибудь отправить ссылку еще раз? Благодаря! –

+0

@ChristianAmmer I _think_ это был [этот] (http://c2.com/cgi/wiki?PrematureOptimization). Тем не менее, есть несколько «семенных частей» вокруг одного и того же предмета, плавающего вокруг межотраслевых элементов. – sehe

ответ

7

Я предлагаю подход на основе итератора/обратного вызова в тех случаях, когда вы требуете наиболее легкого решения.

Причина заключается в том, что разъединяет поставщика от характера использования в потребителем.

В частности, хлопнув результат в коллекцию (даже если результат может быть «оптимизированной» - скорее всего, в (N) РВО или перемещение вместо копирования объекта) будет по-прежнему выделять полный контейнер для полной мощности ,

Edit: отличное дополнение к «обязательные документы» (они не, они просто невероятно полезно, если вы хотите, чтобы понять вещи): Want Speed? Pass By value Дейва Абрахамс.

Теперь

  • это перебор, если потребитель фактически прекращает обработку данных после первых нескольких элементов

    for(auto f=myType.begin(), l=myType.end(); f!=l; ++f) 
    { 
        if (!doProcessing(*f)) 
         break; 
    } 
    
  • это может быть неоптимальным даже если потребитель обрабатывает все элементы в конечном итоге: там может не быть b e необходимо, чтобы все элементы были скопированы в какой-либо конкретный момент, поэтому «слот» для «текущего элемента» можно повторно использовать, уменьшая требования к памяти, увеличивая местность кеша. Например.:

    for(auto f=myType.begin(), l=myType.end(); f!=l; ++f) 
    { 
        myElementType const& slot = *f; // making the temp explicit 
        doProcessing(slot); 
    } 
    

Обратите внимания, что итераторы интерфейсы просто еще выше, если потребителясделал хочет коллекцию, содержащую все элементы:

std::vector<myElementType> v(myType.begin(), myType.end()); 

// look: the client gets to _decide_ what container he wants! 
std::set<myElementType, myComparer> s(myType.begin(), myType.end()); 

попытаться получить эту гибкость иначе.

Наконец, есть некоторые элементы стиля:

  • по своей природе это легко разоблачить (константные) ссылки на элементы с помощью итераторов; это значительно облегчает устранение объекта нарезки и позволяет клиентам использовать элементы полиморфно.

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

  • , если вы будете придерживаться требований диапазона, ориентированного на в C++ 11 вы можете иметь некоторые синтаксический сахар:

    for (auto& slot : myType) 
    { 
        doProcessing(slot); 
    } 
    

Наконец, (как показано выше), в общем смысле итераторы прекрасно работают со стандартной библиотекой.


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

2

первый вещь (вы как-то не упоминается вообще) я бы думать о

const CollectionClass& MessageSpy::getMessages() 

Обратите внимание на &. Это возвращает вам const-ссылку, которая не может быть изменена, но может быть свободно принята.

Без копирования, если вы действительно не хотите копировать.

Если это не подходит, Qt, например, использует "implicit data sharing" для множества классов. I.e. ваши классы «kinda» возвращаются по значению, НО их внутренние данные разделяются до тех пор, пока вы не попытаетесь выполнить операцию записи на одном из них. В этом случае класс, который вы пытаетесь записать, выполняет глубокую копию, и данные перестают использоваться. Это означает, что меньше данных перемещается.

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

Я бы не рекомендовал «итераторы», потому что использование их на компиляторе C++ 03 без auto ключевое слово is the royal pain в # & @. Длинные имена или много typedefs. Вместо этого я вернул бы ссылку const на контейнер.

+0

+1 для linq с обменом данными Qt – sehe

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