по желанию vs. необязательный <T>& - практические примеры? - PullRequest
1 голос
/ 18 июня 2020

Я читал о std::optional<std::reference_wrapper<T>> как о способе передачи необязательных ссылок.

Однако я не могу придумать практического примера, где я бы сделал это вместо того, чтобы просто использовать optional<T>&.

Например, предположим, что я пишу функцию, которая должна принимать необязательный vector<int> по ссылке.

Я мог бы сделать это:

void f(optional<reference_wrapper<vector<int>>> v) {
    // ...
}

int main() {
    vector<int> v = {1, 2, 3, 4};
    f(make_optional<reference_wrapper<vector<int>>>(std::ref(v));
    return 0;
}

Но почему бы просто не сделать это?

void f(optional<vector<int>>& v) {
    // ...
}

int main() {
    f(make_optional<vector<int>>>(std::initializer_list{1, 2, 3, 4}));
    return 0;
}

Приведите пример, где optional<reference_wrapper<T>> предпочтительнее optional<T>&. Различия семанти c, и особенно способы их использования на практике, мне не ясны.

Ответы [ 3 ]

2 голосов
/ 18 июня 2020

std::optional<T> & - ссылка на необязательный объект, который может владеть объектом T. Вы можете изменить T (если он содержится) или очистить необязательный объект, который был передан по ссылке, уничтожив содержащийся T.


std::optional<std::reference_wrapper<T>> - необязательный объект который может владеть ссылкой на T, , но фактически не владеет самим T. T живет вне объекта std::optional. Вы можете изменить T (если ссылка содержится) или очистить необязательный объект, который не уничтожает T. Вы также можете указать необязательный объект на другой T, но это было бы бессмысленно, поскольку вызывающий передает вам необязательный по значению.

Обратите внимание, что мы уже имеют встроенный в язык тип, который означает «необязательная ссылка на T»: T*. И необработанный указатель, и необязательная ссылка имеют в основном одинаковую семантику: вы либо ничего не получаете, либо получаете дескриптор объекта, которым вы не владеете. В современном C ++ необработанный указатель - это способ express необязательного значения, не принадлежащего получателю.

Я не могу придумать ни одной причины, по которой я когда-либо явно использовал бы std::optional<std::reference_wrapper<T>> вместо T*.

1 голос
/ 18 июня 2020

Но почему бы просто не сделать это?

Потому что ваш код не компилируется. make_optional возвращает prvalue, и вы не можете передать prvalue в функцию, которая принимает ссылку, отличную от const lvalue.

Это важно, потому что показывает фундаментальную разницу между этими двумя случаями. Если у вас уже есть T или ссылка на T откуда-то еще, то вы не можете передать это функции, которая принимает optional<T>&. Вам нужно будет скопировать T в переменную optional<T>, а затем передать в функцию ссылку на переменную optional.

Вы не сможете изменить внешний мир T. И в этом разница: с reference_wrapper<T> вы могли бы.

Или, если у вас есть функция, которая может работать с изменяемым T или без него, вы можете просто передать T*, как и большинство людей.

0 голосов
/ 18 июня 2020

Как указано в других ответах, эти два типа служат разным целям, поскольку относятся к разным вещам (ссылка на optional в одном случае и ссылка на vector в другом). Вместо того, чтобы повторять объяснение, вот код, с которым вы можете поиграть, чтобы увидеть функциональные различия.

#include <iostream>
#include <vector>
#include <functional>
#include <optional>


// For better readability:
using optional_reference_vector = std::optional<std::reference_wrapper<std::vector<int>>>;
using optional_vector           = std::optional<std::vector<int>>;


void f(optional_reference_vector v) {
    v->get().push_back(5);
}

void g(optional_vector & w) {
    w->push_back(5);
}

int main() {
    // Two identical vectors with which to work:
    std::vector<int> v = {1, 2, 3, 4};
    std::vector<int> w = {1, 2, 3, 4};

    // Demonstrate an optional reference to a vector
    // ---------------------------------------------
    // Create a reference to `v` in `opt_v`.
    // Changes to `opt_v` will be reflected in `v` (and vice versa).
    optional_reference_vector opt_v {std::ref(v)};
    v.clear();
    // A copy of `opt_v` will be made in f(). Since we are copying a reference to
    // a vector and not the vector itself, the vector in main() is changed by f().
    f(opt_v);
    // Both `v` and `opt_v` refer to the same vector, so the size is the same.
    std::cout << "Using a reference to the vector:\n"
              << "Original vector size: " << v.size() << '\n'
              << "Optional vector size: " << opt_v->get().size() << "\n\n";

    // Demonstrate a reference to an optional vector
    // ---------------------------------------------
    // Copy `w` into `opt_w`.
    // Changes to `opt_w` have no effect on `w` (and vice versa).
    optional_vector opt_w {w};
    w.clear();
    // A reference to `opt_w` will be used in g(), so `opt_w` is updated.
    g(opt_w);
    // There are two vectors that now have different sizes.
    std::cout << "Using a copy of the vector:\n"
              << "Original vector size: " << w.size() << '\n'
              << "Optional vector size: " << opt_w->size() << '\n';

    return 0;
}

Результат этого кода:

Using a reference to the vector:
Original vector size: 1
Optional vector size: 1

Using a copy of the vector:
Original vector size: 0
Optional vector size: 5
...