Почему создание файла initializer_list вызывает копии? [Дубликат]

У меня есть другая перспектива ответить на это.

При работе на разных уровнях, например, в приложении MVC, контроллеру нужны службы для вызова бизнес-операций. В таких сценариях контейнер инжекции зависимостей может использоваться для инициализации служб, чтобы исключить исключение NullReferenceException. Это означает, что вам не нужно беспокоиться о проверке нулевого значения и просто вызвать службы с контроллера, как будто они всегда будут доступны (и инициализированы) как одиночный или прототип.

public class MyController
{
    private ServiceA serviceA;
    private ServiceB serviceB;

    public MyController(ServiceA serviceA, ServiceB serviceB)
    {
        this.serviceA = serviceA;
        this.serviceB = serviceB;
    }

    public void MyMethod()
    {
        // We don't need to check null because the dependency injection container 
        // injects it, provided you took care of bootstrapping it.
        var someObject = serviceA.DoThis();
    }
}
77
задан fredoverflow 19 November 2011 в 11:26
поделиться

6 ответов

Нет, это не будет работать по назначению; вы все равно получите копии. Я очень удивлен этим, так как я думал, что initializer_list существует для хранения массива временных рядов, пока они не будут move 'd.

begin и end для initializer_list return const T *, поэтому результат move в вашем коде T const && - неизменяемая ссылка rvalue. Из этого выражения нельзя толкнуть. Он будет привязан к функциональному параметру типа T const &, потому что rvalues ​​связывается с константными ссылками lvalue, и вы все равно увидите семантику копирования.

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

Обновление: я написал предложение ISO для поддержки initializer_list типов только для перемещения. , Это только первый проект, и он еще не реализован, но вы можете увидеть его для более подробного анализа проблемы.

75
ответ дан Potatoswatter 31 August 2018 в 22:39
поделиться

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

#include <vector>
#include <utility>

// begin helper functions

template <typename T>
void add_to_vector(std::vector<T>* vec) {}

template <typename T, typename... Args>
void add_to_vector(std::vector<T>* vec, T&& car, Args&&... cdr) {
  vec->push_back(std::forward<T>(car));
  add_to_vector(vec, std::forward<Args>(cdr)...);
}

template <typename T, typename... Args>
std::vector<T> make_vector(Args&&... args) {
  std::vector<T> result;
  add_to_vector(&result, std::forward<Args>(args)...);
  return result;
}

// end helper functions

struct S {
  S(int) {}
  S(S&&) {}
};

void bar(S&& s) {}

template <typename T, typename... Args>
void foo(Args&&... args) {
  std::vector<T> args_vec = make_vector<T>(std::forward<Args>(args)...);
  for (auto& arg : args_vec) {
    bar(std::move(arg));
  }
}

int main() {
  foo<S>(S(1), S(2), S(3));
  return 0;
}

Шаблоны Variadic могут обрабатывать ссылки r-значения соответственно, в отличие от initializer_list.

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

0
ответ дан Community 31 August 2018 в 22:39
поделиться

Это не будет работать, как указано, потому что list.begin() имеет тип const T *, и вы не можете перемещаться из постоянного объекта. Возможно, разработчики языка сделали это так, чтобы списки инициализаторов содержали, например, строковые константы, из которых было бы неуместно перемещаться.

Однако, если вы находитесь в ситуации, когда вы знаете, что инициализатор список содержит выражения rvalue (или вы хотите заставить пользователя писать их), тогда есть трюк, который заставит его работать (я был вдохновлен ответом Sumant для этого, но решение проще, чем это). Вам нужно, чтобы элементы, хранящиеся в списке инициализаторов, были не T, а значениями, которые инкапсулируют T&&. Тогда даже если эти значения сами являются const квалифицированными, они все равно могут получить модифицируемое значение rvalue.

template<typename T>
  class rref_capture
{
  T* ptr;
public:
  rref_capture(T&& x) : ptr(&x) {}
  operator T&& () const { return std::move(*ptr); } // restitute rvalue ref
};

Теперь вместо объявления аргумента initializer_list<T> вы объявляете аргумент initializer_list<rref_capture<T> >. Вот конкретный пример, связанный с вектором std::unique_ptr<int> интеллектуальных указателей, для которых определена только семантика перемещения (поэтому сами эти объекты никогда не могут быть сохранены в списке инициализаторов); но список инициализаторов ниже компилируется без проблем.

#include <memory>
#include <initializer_list>
class uptr_vec
{
  typedef std::unique_ptr<int> uptr; // move only type
  std::vector<uptr> data;
public:
  uptr_vec(uptr_vec&& v) : data(std::move(v.data)) {}
  uptr_vec(std::initializer_list<rref_capture<uptr> > l)
    : data(l.begin(),l.end())
  {}
  uptr_vec& operator=(const uptr_vec&) = delete;
  int operator[] (size_t index) const { return *data[index]; }
};

int main()
{
  std::unique_ptr<int> a(new int(3)), b(new int(1)),c(new int(4));
  uptr_vec v { std::move(a), std::move(b), std::move(c) };
  std::cout << v[0] << "," << v[1] << "," << v[2] << std::endl;
}

На один вопрос нужен ответ: если элементы списка инициализаторов должны быть истинными prvalues ​​(в примере они являются значениями x), обеспечивает ли язык что время жизни соответствующих временных рядов продолжается до той точки, где они используются? Честно говоря, я не думаю, что соответствующий раздел 8.5 стандарта затрагивает эту проблему вообще. Однако, прочитав 1.9: 10, кажется, что соответствующее полноэкранное выражение во всех случаях включает использование списка инициализаторов, поэтому я думаю, что нет никакой опасности обманывать ссылки rvalue.

2
ответ дан Marc van Leeuwen 31 August 2018 в 22:39
поделиться
bar(std::move(*it));   // kosher?

Не так, как вы намереваетесь. Вы не можете переместить объект const. И std::initializer_list обеспечивает доступ к своим элементам const. Таким образом, тип it равен const T *.

Ваша попытка вызвать std::move(*it) приведет только к l-значению. IE: копия.

std::initializer_list ссылается на статическую память. Для этого и предназначен класс. Вы не можете перемещать из статической памяти, потому что движение подразумевает его изменение. Вы можете копировать только его.

16
ответ дан Nicol Bolas 31 August 2018 в 22:39
поделиться

Я подумал, что поучительно предложить разумную отправную точку для обхода.

Комментарии inline.

#include <memory>
#include <vector>
#include <array>
#include <type_traits>
#include <algorithm>
#include <iterator>

template<class Array> struct maker;

// a maker which makes a std::vector
template<class T, class A>
struct maker<std::vector<T, A>>
{
  using result_type = std::vector<T, A>;

  template<class...Ts>
  auto operator()(Ts&&...ts) const -> result_type
  {
    result_type result;
    result.reserve(sizeof...(Ts));
    using expand = int[];
    void(expand {
      0,
      (result.push_back(std::forward<Ts>(ts)),0)...
    });

    return result;
  }
};

// a maker which makes std::array
template<class T, std::size_t N>
struct maker<std::array<T, N>>
{
  using result_type = std::array<T, N>;

  template<class...Ts>
  auto operator()(Ts&&...ts) const
  {
    return result_type { std::forward<Ts>(ts)... };
  }

};

//
// delegation function which selects the correct maker
//
template<class Array, class...Ts>
auto make(Ts&&...ts)
{
  auto m = maker<Array>();
  return m(std::forward<Ts>(ts)...);
}

// vectors and arrays of non-copyable types
using vt = std::vector<std::unique_ptr<int>>;
using at = std::array<std::unique_ptr<int>,2>;


int main(){
    // build an array, using make<> for consistency
    auto a = make<at>(std::make_unique<int>(10), std::make_unique<int>(20));

    // build a vector, using make<> because an initializer_list requires a copyable type  
    auto v = make<vt>(std::make_unique<int>(10), std::make_unique<int>(20));
}
0
ответ дан Richard Hodges 31 August 2018 в 22:39
поделиться

Рассмотрим in<T> идиому, описанную на cpptruths . Идея состоит в том, чтобы определить lvalue / rvalue во время выполнения, а затем вызвать move или copy-construction. in<T> будет определять значение rvalue / lvalue, даже если стандартный интерфейс, предоставляемый параметром initializer_list, является ссылкой на константу.

-1
ответ дан Sumant 31 August 2018 в 22:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: