Можно ли взять параметр по константной ссылке, при этом запретив преобразования, чтобы вместо этого не передавались временные значения? - PullRequest
5 голосов
/ 25 января 2012

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

Сбой вызова bar в этом коде.Это желательно, потому что ссылка имеет неправильный тип.Вызов bar_const также имеет неправильный тип, но он автоматически компилируется.Это нежелательно для меня.

#include<vector>
using namespace std;

int vi;

void foo(int &) { }
void bar(long &) { }
void bar_const(const long &) { }

int main() {
   foo(vi);
   // bar(vi); // compiler error, as expected/desired
   bar_const(vi);
}

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

(Очевидно, int и long - это очень маленькие типы. Но я был замечен с более крупными структурами, которые можно преобразовывать друг в друга.Я не хочу, чтобы это происходило тихо, когда я использую константную ссылку. Иногда пометка конструкторов как явных подсказок помогает, но это не идеально)

Обновление: Я представляюсистема, подобная следующей: представьте, что у вас есть две функции X byVal(); и X& byRef(); и следующий блок кода:

 X x;
 const_lvalue_ref<X> a = x; // I want this to compile
 const_lvalue_ref<X> b = byVal(); // I want this to fail at compile time
 const_lvalue_ref<X> c = byRef(); // I want this to compile

Этот пример основан на локальных переменных, но я хочу, чтобы он также работал с параметрами,Я хочу получить какое-то сообщение об ошибке, если я случайно передаю ref-to-временный или ref-to-a-copy, когда мне кажется, что я передаю что-то более легкое, например, ref-to-lvalue.Это просто «стандарт кодирования» - если я на самом деле хочу разрешить передачу ссылки на временный объект, то я буду использовать простой const X&.(Я считаю этот фрагмент на FOREACH Boost весьма полезным.)

Ответы [ 6 ]

7 голосов
/ 25 января 2012

Хорошо, если ваш «большой параметр» является классом, первое, что нужно сделать, это убедиться, что вы пометили все явные конструкторы параметров (кроме конструктора копирования):

class BigType
{
public:
    explicit BigType(int);
};

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

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

Если это не сработает, вы можете использовать некоторые шаблоны магии, такие как:

template <typename T>
void func(const T &); // causes an undefined reference at link time.

template <>
void func(const BigType &v)
{
    // use v.
}
3 голосов
/ 25 января 2012

Если вы можете использовать C ++ 11 (или его части), это просто:

void f(BigObject const& bo){
  // ...
}

void f(BigObject&&) = delete; // or just undefined

Живой пример на Ideone .

Это будет работать, потому что привязка к r-значению ref предпочтительнее привязки к ссылке на const для временного объекта.

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

struct BigObjWrapper{
  BigObjWrapper(BigObject const& o)
    : object(o) {}

  BigObject const& object;
};

void f(BigObjWrapper wrap){
  BigObject const& bo = wrap.object;
  // ...
}

Живой пример на Ideone .

2 голосов
/ 25 января 2012

Это довольно просто решить: прекратить принимать значения по ссылке.Если вы хотите убедиться, что параметр адресуем , то сделайте его address :

void bar_const(const long *) { }

Таким образом, пользователь должен передать указатель.И вы не можете получить указатель на временный (если только пользователь не является ужасно злонамеренным).

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

возможно, я возьму его адрес, не осознавая, что я, по сути, беру адрес временного.

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

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

Взятие const* может быть проблематичным.Зачем?Потому что вы не знаете, откуда взялся этот указатель.Кому принадлежит этот указатель?const& имеет ряд синтаксических мер защиты, предотвращающих от вас плохие поступки (если вы не берете его адрес).const* не имеет ничего;Вы можете скопировать этот указатель на содержание вашего сердца.Ваш интерфейс ничего не говорит о том, разрешено ли вам владеть объектом или передавать право собственности другим лицам.

Эта неоднозначность объясняет, почему в C ++ 11 есть умные указатели, такие как unique_ptr и shared_ptr.Эти указатели могут описывать реальные отношения владения памятью.

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

В случае, если вы не используете C ++ 11, вы должны использовать интеллектуальные указатели Boost для достижения аналогичных эффектов.*

1 голос
/ 25 января 2012

Вы не можете, и даже если бы вы могли, это, вероятно, не сильно помогло бы.Подумайте:

void another(long const& l)
{
    bar_const(l);
}

Даже если бы вы могли как-то предотвратить привязку к временному в качестве входных данных для bar_const, такие функции, как another, могли бы вызываться со ссылкой, привязанной к временному, и вы завершилив той же ситуации.

Если вы не можете принять временное, вам нужно использовать ссылку на неконстантный или указатель:

void bar_const(long const* l);

требуетсяЗначение для его инициализации.Конечно, такая функция, как

void another(long const& l)
{
    bar_const(&l);
}

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

0 голосов
/ 01 февраля 2012

(Отвечая на мой собственный вопрос благодаря этому замечательному ответу на другой вопрос, который я задал. Спасибо @hvd.)

Короче говоря, пометка параметра функции как volatile означает, что онне может быть привязан к значению(Кто-нибудь может придумать стандартную цитату для этого? Временные могут быть связаны с const&, но не с const volatile &, очевидно. Это то, что я получаю на g ++ - 4.6.1. (Дополнительно: см. этот расширенный комментарийstream для некоторых кровавых подробностей, которые way над моей головой :-)))

void foo( const volatile Input & input, Output & output) {
}

foo(input, output); // compiles. good
foo(get_input_as_value(), output); // compile failure, as desired.

Но , вы не на самом деле хочет, чтобы параметры были volatile.Поэтому я написал небольшую оболочку для const_cast на volatile.Таким образом, подпись foo становится такой:

void foo( const_lvalue<Input> input, Output & output) {
}

где обертка:

template<typename T>
struct const_lvalue {
    const T * t;
    const_lvalue(const volatile T & t_) : t(const_cast<const T*>(&t_)) {}
    const T* operator-> () const { return t; }
};

Это может быть создано только из lvalue

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

Я надеюсь привыкнуть делать это со всеми подходящими параметрами по умолчанию.

Демонстрация на ideone

0 голосов
/ 25 января 2012

Я думаю, что ваш пример с int и long является чем-то вроде красной сельди, поскольку в каноническом C ++ вы никогда не будете передавать встроенные типы по константной ссылке: вы передаете их по значению или неконстантной ссылке.

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

class Foo
{
    explicit Foo(int bar) { }
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...