У меня есть другая перспектива ответить на это.
При работе на разных уровнях, например, в приложении 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();
}
}
Нет, это не будет работать по назначению; вы все равно получите копии. Я очень удивлен этим, так как я думал, что 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
типов только для перемещения. , Это только первый проект, и он еще не реализован, но вы можете увидеть его для более подробного анализа проблемы.
В текущем стандарте, как , уже не ответил , он не разрешен. Вот еще одно обходное решение для достижения чего-то подобного, определяя функцию как вариационную, вместо того, чтобы брать список инициализаторов.
#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.
В этом примере кода я использовал набор небольших вспомогательных функций для преобразования вариационных аргументов в вектор, чтобы сделать его похожим на исходный код. Но, конечно, вы можете написать рекурсивную функцию с вариационными шаблонами напрямую.
Это не будет работать, как указано, потому что 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.
bar(std::move(*it)); // kosher?
Не так, как вы намереваетесь. Вы не можете переместить объект const
. И std::initializer_list
обеспечивает доступ к своим элементам const
. Таким образом, тип it
равен const T *
.
Ваша попытка вызвать std::move(*it)
приведет только к l-значению. IE: копия.
std::initializer_list
ссылается на статическую память. Для этого и предназначен класс. Вы не можете перемещать из статической памяти, потому что движение подразумевает его изменение. Вы можете копировать только его.
Я подумал, что поучительно предложить разумную отправную точку для обхода.
Комментарии 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));
}
Рассмотрим in<T>
идиому, описанную на cpptruths . Идея состоит в том, чтобы определить lvalue / rvalue во время выполнения, а затем вызвать move или copy-construction. in<T>
будет определять значение rvalue / lvalue, даже если стандартный интерфейс, предоставляемый параметром initializer_list, является ссылкой на константу.