Проблема с возвратом аргументов, которые являются константными ссылками - PullRequest
2 голосов
/ 03 декабря 2009

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

#include <iostream>
#include <string>
using namespace std;

string ss("hello");

const string& fun(const string& s) {
        return s;
}

int main(){
        const string& s = fun("hello");
        cout<<s<<endl;
        cout<<fun("hello")<<endl;
}

Первый кут не будет работать. второй кут будет.

Меня беспокоит следующее:

Разве невозможно представить ситуацию, когда разработчик метода хочет вернуть аргумент, который является ссылкой const и неизбежен?
Я думаю, что это вполне возможно.
Что бы вы сделали в C ++ в этой ситуации?

Спасибо.

Ответы [ 8 ]

5 голосов
/ 03 декабря 2009

В C ++ важно установить время жизни объектов. Одним из распространенных методов является выбор «владельца» для каждого объекта. Владелец отвечает за то, чтобы объект существовал столько, сколько ему нужно, и удалял его, когда он не нужен.

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

В вашем примере нет явного владения строковым объектом. Он не принадлежит функции main (), поскольку он не является локальной переменной и другого владельца нет.

3 голосов
/ 03 декабря 2009

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

К счастью, тонкая ошибка решена в c ++ 0x. Всегда возвращайте по значению. Новые конструкторы перемещения сделают все так быстро, как вы могли пожелать.

3 голосов
/ 03 декабря 2009

Техника действительна и используется постоянно. Однако в первом примере вы конвертируете const char* в временный std::string и пытаетесь его вернуть, что не то же самое, что возвращать const-ссылку на объект, хранящийся в другом месте. Во втором примере вы делаете то же самое, но вы используете результат до уничтожения временного объекта, что в данном случае является законным, но опасным (см. Ваш первый случай.)

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

2 голосов
/ 03 декабря 2009

Я думаю, что это небольшая слабость C ++. Есть неудачное сочетание двух факторов:

  • Функция верна только в том случае, если ее аргумент равен.
  • Неявное преобразование означает, что аргументом функции является , а не объектом, которым она может казаться.

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

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

const char *fun(const char *s) { return s; }

Одно только это изменение означает, что код, который ранее был плохим, работает. Поэтому я думаю, что в этом случае это хорошая идея. Конечно, это не поможет, если кто-то определит тип, о котором автор fun никогда не слышал, и который имеет operator std::string(). Кроме того, fun не является реалистичной функцией, и для более полезных подпрограмм вы, возможно, не захотите предоставлять эквивалент, который работает на char*. В этом случае void fun(const char *); по крайней мере заставляет вызывающую функцию явно приводить к строке, что может помочь им правильно использовать функцию.

В качестве альтернативы, вызывающий абонент может заметить, что он предоставляет char* и возвращает ссылку на string. Мне кажется, это бесплатный обед, так что должны прозвенеть тревожные колокольчики, откуда взялась эта строка и как долго она продлится.

0 голосов
/ 03 декабря 2009

Я думаю, что вы просите неприятностей в C ++ 98:)

Это можно решить двумя способами. Во-первых, вы можете использовать общий указатель. В этом случае память будет автоматически управляться shared_ptr, и все готово! Но это плохое решение в большинстве случаев. Потому что вы на самом деле не разделяете память между многими ссылками. auto_ptr является истинным решением этой проблемы, если вы все время планируете использовать кучу. auto_ptr нужно одно небольшое решающее улучшение, которого нет в C ++ 98, чтобы его можно было реально использовать, а именно: Move Semantic!

Лучшее решение состоит в том, чтобы разрешить перемещение владельца между ссылками, используя ссылки на r-значения, которые есть в C ++ 0x. Итак, ваш кусок кода будет выглядеть (не уверен, что синтаксис правильный):

string fun(const string& s) {
        return s; // return a copy of s
}
....
string s = fun("Hello"); // the actual heap memory is transfered to s.
                         // the temporary is destroyed, but as we said
                        // it is empty, because 's' now owns the actual data!
0 голосов
/ 03 декабря 2009

В грядущем стандарте C ++ ссылки на r-значения могут использоваться для поддержания ваших временных объектов в «живом состоянии» и решения вашей проблемы.

Возможно, вы захотите найти совершенную пересылку и переместить конструкторов.

0 голосов
/ 03 декабря 2009

Разве невозможно представить ситуацию, когда разработчик метода хочет вернуть аргумент, который является ссылкой const и неизбежен?

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

Обычно и требуется для документирования времени жизни возвращенных указателей и ссылок, например, для std :: string :: data.

Что бы вы сделали в C ++ в этой ситуации?

Часто вы можете передавать и возвращать по значению вместо . Обычно это делается с помощью таких вещей, как std :: copy (в данном случае для целевого итератора).

0 голосов
/ 03 декабря 2009

Да, я согласен, что бывают ситуации, когда это актуальная проблема.

Я бы использовал указатель с подсчетом ссылок, чтобы "решить" его.

...