Предотвратить передачу временного объекта

У меня есть класс, который "запоминает" ссылку на некоторый объект (например, целочисленная переменная). Я не могу заставить его ссылаться на значение, которое немедленно уничтожается, и я ищу способ защитить пользователей моего класса от случайных действий.

Является ли перегрузка rvalue-reference хорошим способом предотвращения передачи временного объекта?

struct HasRef {
    int& a;
    HasRef(int& a):a(a){}
    void foo(){ a=1; }
};


int main(){
    int x=5;
    HasRef r1(x);
    r1.foo();  // works like intended.

    HasRef r2(x+4);
    r2.foo(); // dereferences the temporary created by x+4

 }

Будет ли частная перегрузка rvalue делать?

 struct HasRef {
   int& a;
   HasRef( int& a ):a(a){}
   void foo(){ a=1; }
 private: 
   HasRef( int&& a );
 };

 ... HasRef r2(x+1); // doesn't compile => problem solved?

Есть ли подводные камни, которых я не видел?

4 ответа

Если вам нужно хранить const ссылка на некоторый экземпляр типа B в ваш класс A, то наверняка вы хотите быть уверены, что время жизни A экземпляр будет превышен временем жизни B пример:

B b{};
A a1{b}; // allowed
A a2{B{}}; // should be denied
B const f() { return B{}; } // const result type may make sense for user-defined types
A a3{f()}; // should also be denied!

Чтобы сделать это возможным, вы должны явно = delete; все перегрузки конструктора, которые могут принимать значения (оба const && а также &&). Для этого нужно просто = delete; только const && версия конструктора.

struct B {};

struct A
{
    B const & b;
    A(B const & bb) : b(bb) { ; } // accepts only `B const &` and `B &`
    A(B const &&) = delete; // prohibits both `B &&` and `B const &&`
};

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

Это также работает для встроенных скаляров. Например, double const f() { return 0.01; }хотя это вызывает предупреждение вроде:

предупреждение: спецификатор типа 'const' на тип возвращаемого значения не имеет никакого эффекта [-Wignored-qualifiers]

это все еще может иметь эффект, если вы просто = delete; только && версия конструктора:

struct A
{
    double const & eps;
    A(double const & e) : eps(e) {} // binds to `double const &`, `double &` AND ! `double const &&`
    A(double &&) = delete; // prohibit to binding only to `double &&`, but not to `double const &&`
};

double const get_eps() { return 0.01; }

A a{0.01}; // hard error
A a{get_eps()}; // no hard error, but it is wrong!

Для неконверсионных конструкторов (т.е. не унарных) есть проблема: возможно, вам придется предоставить = delete;-d версии для всех комбинаторно возможных версий конструкторов:

struct A
{
    A(B const &, C const &) {}
    A(B const &&, C const &&) = delete;
    // and also!
    A(B const &, C const &&) = delete;
    A(B const &&, C const &) = delete;
};

запретить смешанные дела, такие как:

B b{};
A a{b, C{}};

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

В C++11 я предпочел бы удаленную функцию частной функции. Это немного более ясно, что вы действительно не можете назвать это (даже если вы член или друг класса).

NB, если удаленный конструктор HasRef(int&&)=delete это не будет выбрано здесь:

int i;
HasRef hr(std::forward<const int>(i));

С аргументом типа const int&& HasRef(const int&) конструктор будет использоваться, а не HasRef(int&&) один. В этом случае все будет хорошо, потому что i действительно является lvalue, но в общем случае это может быть не так, так что это может быть один из очень редких случаев, когда полезна ссылка на const rvalue:

HasRef(const int&&) = delete;

Это не должно компилироваться. Хороший компилятор C++ (или практически любой компилятор C++, который я когда-либо видел) остановит это.

Я предполагаю, что вы компилируете в MSVS. В этом случае отключите языковые расширения, и вы получите сообщение об ошибке.

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

Другие вопросы по тегам