Як вирішити звисаючий const ref


18

Наступна коротка програма

#include <vector>
#include <iostream>

std::vector<int> someNums()
{
    return {3, 5, 7, 11};
}

class Woop
{
public:
    Woop(const std::vector<int>& nums) : numbers(nums) {}
    void report()
    {
        for (int i : numbers)
            std::cout << i << ' ';
        std::cout << '\n';
    }
private:
    const std::vector<int>& numbers;
};

int main()
{
    Woop woop(someNums());
    woop.report();
}

є звисаюча довідкова проблема, про яку, схоже, жоден компілятор не попереджає. Проблема полягає в тому, що тимчасові журнали можуть бути прив’язані до const-refs, які ви можете потім тримати навколо. Тоді питання; Чи існує метод уникнути попадання в цю проблему? Переважно той, який не передбачає жертвувати правильністю конкуренції або завжди робити копії великих об'єктів.


4
Це хитро. Я можу запевнити вас, що я подумаю двічі, перш ніж зробити посилання на const змінної члена. Якщо ви сумніваєтесь, я б хотів якось моделювати ці дані, щоб інтелектуальний покажчик міг бути залучений (або std::unique_ptrдля ексклюзивного володіння, std::shared_ptrабо для спільного володіння, або std::weak_ptr, принаймні, для визнання втрачених даних).
Scheff

У C ++ ви не платите за те, що вам не потрібно / використовувати. Програміст повинен подбати про те, щоб термін служби згаданого об'єкта не закінчувався, поки посилання ще використовується / існує. Те ж саме і для сировинних покажчиків, ... Є розумні покажчики, які приносять вам функції, про які ви просили :)
Fareanor

2
Учасники довідників завжди помиляються: biljeutter.com/2020/02/23/references-simply
Максим

Хоча компілятор не попереджає, цю помилку можна знайти за допомогою Valgrind та -fsanitize=address. Я не думаю, що існує жодна найкраща практика, щоб цього уникати без шкоди для виконання.
ks1322

Відповіді:


8

У ситуації, коли якийсь метод зберігає посилання після повернення, корисно використовувати std::reference_wrapperзамість звичайного посилання:

#include <functional>

class Woop
{
public:
    using NumsRef = ::std::reference_wrapper<const std::vector<int>>;
    Woop(NumsRef nums) : numbers_ref{nums} {}
    void report()
    {
        for (int i : numbers_ref.get())
            std::cout << i << ' ';
        std::cout << '\n';
    }
private:
    NumsRef numbers_ref;
};
  1. він уже поставляється з набором перевантажень, що запобігають прив'язці значень і непередбачуваному проходженню тимчасовості, тому немає необхідності турбуватися з додатковою забороненою перевантаженням, приймаючи ревальвацію Woop (std::vector<int> const &&) = delete;для вашого методу:
Woop woop{someNums()}; // error
woop.report();
  1. це дозволяє приховати прив'язку значень lvalues, щоб не порушити існуючі дійсні виклики:
auto nums{someNums()};
Woop woop{nums}; // ok
woop.report();
  1. це дозволяє чітко прив’язувати значення, що є хорошою практикою, щоб вказати, що абонент буде зберігати посилання після повернення:
auto nums{someNums()};
Woop woop{::std::ref(nums)}; // even better because explicit
woop.report();

10

Одним із способів зробити ваш клас менш вразливим може бути додавання видаленого конструктора, який займає правильну корекцію. Це не дозволить екземпляру вашого класу зробити прив’язку до тимчасових.

Woop(std::vector<int>&& nums)  =delete;

Цей видалений конструктор насправді змусить код O / P не компілювати, що може бути поведінкою, яку ви шукаєте?


3

Я погоджуюся з іншими відповідями та зауваженнями, що вам слід добре подумати, якщо вам дійсно потрібно зберігати посилання всередині класу. І якщо ви це зробите, ви, ймовірно, захочете не-const вказівник на вектор const (тобто std::vector<int> const * numbers_).

Однак, якщо це так, я вважаю, що інші відповіді, які наразі розміщені, знаходяться поруч. Всі вони показують вам, як зробити Woopці цінності.

Якщо ви можете запевнити, що вектор, який ви передаєте, пережив ваш Woopекземпляр, тоді ви можете явно відключити побудову a Woopз rvalue. Це можливо за допомогою цього синтаксису C ++ 11:

Woop (std::vector<int> const &&) = delete;

Тепер ваш приклад код більше не збиратиметься. Компілятор з помилкою, схожою на:

prog.cc: In function 'int main()':
prog.cc:29:25: error: use of deleted function 'Woop::Woop(const std::vector<int>&&)'
   29 |     Woop woop(someNums());
      |                         ^
prog.cc:15:5: note: declared here
   15 |     Woop(std::vector<int> const &&) = delete;
      |     ^~~~

PS: Ви, мабуть, хочете явного конструктора, див. Наприклад, що означає явне ключове слово? .


Я, здається, там вкрав вашу відповідь. Вибачте!
Джем Тейлор

1

Щоб запобігти цьому конкретному випадку, ви можете взяти вказівник (оскільки Weep(&std::vector<int>{1,2,3})це не дозволено), або ви можете взяти не-const посилання, яке також буде помилкою на тимчасовій основі.

Woop(const std::vector<int> *nums);
Woop(std::vector<int> *nums);
Woop(std::vector<int>& nums);

Вони все ще не гарантують, що значення залишається дійсним, але принаймні зупиняє найпростішу помилку, не створює копію і не потрібно numsстворювати спеціальним чином (наприклад, як std::shared_ptrабо std::weak_ptrтак).

std::scoped_lockприклад посилання на mutex був би прикладом, і той, де унікальний / спільний / слабкий ptr не дуже потрібен. Часто std::mutexзаповіт буде просто основним членом або локальною змінною. Ви все ще повинні бути дуже обережними, але в цих випадках загалом легко визначити тривалість життя.

std::weak_ptrє ще одним варіантом невлаштування, але тоді ви змушуєте абонента використовувати shared_ptr(і, таким чином, також купувати), а іноді цього не потрібно.

Якщо з копією все в порядку, це просто уникає проблеми.

Якщо Woopслід взяти право власності, або передайте як r-значення та перемістіть (і уникайте проблем з покажчиком / посиланням повністю), або використовуйте, unique_ptrякщо ви не можете перемістити саме значення або хочете, щоб покажчик залишався дійсним.

// the caller can't continue to use nums, they could however get `numbers` from Woop or such like
// or just let Woop only manipulate numbers directly.
Woop(std::vector<int> &&nums) 
   : numbers(std::move(nums)) {}
std::vector<int> numbers;

// while the caller looses the unique_ptr, they might still use a raw pointer, but be careful.
// Or again access numbers only via Woop as with the move construct above.
Woop(std::unique_ptr<std::vector<int>> &&nums) 
    : numbers(std::move(nums)) {}
std::unique_ptr<std::vector<int>> numbers;

Або якщо право власності розділено, ви можете використовувати shared_ptrдля всього, і це буде видалено разом із остаточним посиланням, але це може зробити відстеження життєвих циклів об'єкта дуже заплутаним, якщо надмірно використовувати.


1

Ви можете використовувати template programmingі arraysякщо хочете мати об'єкт, який містить constконтейнер. Завдяки constexprконструктору і constexpr arraysви досягаєте const correctnessі compile time execution.

Ось публікація, яка може бути цікавою: std :: перемістити вектор const

#include <array>
#include <iostream>
#include <vector>


std::array<int,4>  someNums()
{
    return {3, 5, 7, 11};
}


template<typename U, std::size_t size>
class Woop
{
public:

template<typename ...T>
    constexpr Woop(T&&... nums) : numbers{nums...} {};

    template<typename T, std::size_t arr_size>
    constexpr Woop(std::array<T, arr_size>&& arr_nums) : numbers(arr_nums) {};

    void report()
    const {
        for (auto&& i : numbers)
            std::cout << i << ' ';
         std::cout << '\n';
    }



private: 
    const std::array<U, size> numbers;
    //constexpr vector with C++20
};

int main()
{
    Woop<int, 4> wooping1(someNums());
    Woop<int, 7> wooping2{1, 2, 3, 5, 12 ,3 ,51};

    wooping1.report();
    wooping2.report();
    return 0;
}

запустити код

Вихід:

3 5 7 11                                                                                                                        
1 2 3 5 12 3 51

1
Завдяки цьому цифри, як std::arrayце, гарантовано копіюються, навіть якщо переміщення було б доступне інакше. Додатково до цього , wooping1і wooping2не той же самий тип, який менше , ніж ідеал.
sp2danny

@ sp2danny дякую за відгуки, і я повинен погодитися з вами в обох питаннях. user7860670 запропонував краще рішення :)
M.Mac
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.