c++ - 将 const_cast 元素移出 std::initializer_list 是否有风险?

标签 c++ c++11

这个问题建立在 @FredOverflow's question .

CLARIFICATION: initializer_list approach is required as the VC++2012 has a bug the prevents forwarded expansion of namespaced arguments. _MSC_VER <= 1700 has the bug.

我编写了一个可变参数模板函数,它可以折叠类型化容器中的任意数量的参数。我使用类型的构造函数将可变参数转换为可使用的值。例如。 _variant_t :)

我的 MySql 需要这个一次性将参数推送到准备好的语句时的 C++ 库,而我的 MySqlVariant将输入数据转换为 MYSQL_BIND s。我可能会与 BLOB 合作s,我想尽可能避免复制构造move&&周围的大容器。

我做了一个简单的测试,发现 initialize_list copy-construct用于存储的元素,并在超出范围时销毁它们。完美...然后我尝试将数据移出 initializer_list而且,令我惊讶的是,它使用了lvalues不是 rvalues正如我所期望的 std::move .

Funny as this happens just after Going Native 2013 clearly warned me that move does not move, forward does not forward... be like water, my friend - to stay on the deep end of thinking.

但这并没有阻止我 :) 我决定 const_cast initializer_list值并仍然将它们移出。需要执行驱逐令。这是我的实现:

template <typename Output_t, typename ...Input_t>
inline Output_t& Compact(Output_t& aOutput, Input_t&& ...aInput){
    // should I do this? makes sense...
    if(!sizeof...(aInput)){
        return aOutput;
    }

    // I like typedefs as they shorten the code :)
    typedef Output_t::value_type Type_t;

    // can be either lvalues or rvalues in the initializer_list when it's populated.
    std::initializer_list<Type_t> vInput = { std::forward<Input_t>(aInput)... };

    // now move the initializer_list into the vector.
    aOutput.reserve(aOutput.size() + vInput.size());
    for(auto vIter(vInput.begin()), vEnd(vInput.end()); vIter != vEnd; ++vIter){
        // move (don't copy) out the lvalue or rvalue out of the initializer_list.
        // aOutput.emplace_back(std::move(const_cast<Type_t&>(*vIter))); // <- BAD!
        // the answer points out that the above is undefined so, use the below
        aOutput.emplace_back(*vIter); // <- THIS is STANDARD LEGAL (copy ctor)!
    }

    // done! :)
    return aOutput;
}

使用它很容易:

// You need to pre-declare the container as you could use a vector or a list...
// as long as .emplace_back is on duty!
std::vector<MySqlVariant> vParams;
Compact(vParams, 1, 1.5, 1.6F, "string", L"wstring",
    std::move(aBlob), aSystemTime); // MySql params :)

我还上传了完整的测试 on IDEone ^显示为 std::string 的内存使用此功能正确移动。 (我会把它全部粘贴在这里,但它有点长......)

只要 _variant_t (或任何最终包装对象) 具有正确的构造函数,这很棒。如果数据可以移出,那就更好了。在我对其进行测试时,它几乎可以正常工作std::move在正确的方向:)

我的问题很简单:

  • 我在标准方面做得对吗?
  • 它工作正常的事实是有意的还是只是副作用?
  • 如果 std::moveinitializer_list 上默认不工作,我在这里做什么:非法、不道德、骇人听闻......还是完全错误

PS:我是自学成才的 Windows Native C++开发者,对标准一无所知。
^ 如果我在这里做非常不标准的事情,我的借口。

更新

谢谢大家,我现在已经有了答案和解决方案(一个短的一个长的)

And I love the C++11 side of SO. Many knowledgeable people here...

最佳答案

不幸的是,在一般情况下,这是未定义的行为。在§8.5.4/5,强调我的:

An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation allocated a temporary array of N elements of type const E, where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array.

您在哪里看到 std::initializer_list<E> , 你可以把它当作 const E[N] .

所以当你 const_cast离开const ,您正在查看对 const 的可变引用目的。对 const 的任何修改对象是未定义的行为。

当你移动它时 std::string ,您正在修改 const目的。不幸的是,未定义行为的一种行为是看似正确的行为。但这在技术上是未定义的。

请注意,当您使用 std::move(int)换成另一个,那个是明确定义的,因为int 的只能被复制,所以移动什么也不做,也没有 const对象被修改。但总的来说,它是未定义的。

关于c++ - 将 const_cast 元素移出 std::initializer_list 是否有风险?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18686991/

相关文章:

c++ - 如何在 C++ 中将指针指定为 "thread_local"存储?

c++ - 构建 Boost 动态可链接会导致链接错误?

c++ - 宏如何工作 'REGISTER_OP("ZeroOut")' 在 Tensorflow 中添加新操作?

c++ - 2个unique_ptr的 map

c++ - 限制仿函数参数类型和常量

c++ - 使用字符串构造定义名称

c++ - 使用模板构建静态(但复杂)的查找表

c++ - 在 constexpr 抛出的异常中使用副作用是否合法?

c++ - 在 C++ 中显示所有基本类型的数据大小的程序

c++ - 在 C++ 中自动引用 vector