reinterpret_casting std :: aligned_storage *到没有std :: launder的T *会违反严格别名规则吗?

时间:2017-12-10 03:44:08

标签: c++ language-lawyer reinterpret-cast strict-aliasing

以下示例来自cppreference.com的std::aligned_storage page

#include <iostream>
#include <type_traits>
#include <string>

template<class T, std::size_t N>
class static_vector
{
    // properly aligned uninitialized storage for N T's
    typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N];
    std::size_t m_size = 0;

public:
    // Create an object in aligned storage
    template<typename ...Args> void emplace_back(Args&&... args) 
    {
        if( m_size >= N ) // possible error handling
            throw std::bad_alloc{};
        new(data+m_size) T(std::forward<Args>(args)...);
        ++m_size;
    }

    // Access an object in aligned storage
    const T& operator[](std::size_t pos) const 
    {
        return *reinterpret_cast<const T*>(data+pos);
    }

    // Delete objects from aligned storage
    ~static_vector() 
    {
        for(std::size_t pos = 0; pos < m_size; ++pos) {
            reinterpret_cast<T*>(data+pos)->~T();
        }
    }
};

int main()
{
    static_vector<std::string, 10> v1;
    v1.emplace_back(5, '*');
    v1.emplace_back(10, '*');
    std::cout << v1[0] << '\n' << v1[1] << '\n';
}

在示例中,operator[]只有reinterpret_cast s std::aligned_storage*T*没有std:launder,并直接执行间接。但是,根据this question,即使创建了T类型的对象,这似乎也未定义。

所以我的问题是:示例程序是否真的违反了严格别名规则?如果没有,我的理解有什么问题?

2 个答案:

答案 0 :(得分:3)

我在ISO C ++标准 - 讨论论坛中询问了a related question。我从这些讨论中得到了答案,并在此写下来希望帮助那些对这个问题感到困惑的人。我将根据这些讨论不断更新这个答案。

P0137之前,请参阅[basic.compound]第3段:

  

如果类型为T的对象位于地址A,那么类型为cv T *的指针(其值为地址A)将被指向该对象,而不管该值是如何获得的。

和[expr.static.cast]第13段:

  

如果原始指针值表示存储器中字节的地址A且A满足T的对齐要求,则结果指针值表示与原始指针值相同的地址,即A。

表达式reinterpret_cast<const T*>(data+pos)表示先前创建的类型为T的对象的地址,因此指向该对象。通过这个指针的间接确实得到了那个定义良好的对象。

然而,在P0137之后,指针值的定义被改变,并且删除了第一个块引用的单词。现在参考[basic.compound]第3段:

  

指针类型的每个值都是以下之一:

     
      
  • 指向对象或函数的指针(指针指向对象或函数),或

  •   
  • ...

  •   

和[expr.static.cast]第13段:

  

如果原始指针值表示存储器中字节的地址A且A不满足T的对齐要求,则未指定结果指针值。否则,如果原始指针值指向对象a,并且存在类型为T(忽略cv-qualification)的对象b,该对象b是指针可互换的,则结果是指向b的指针。 否则,转换时指针值不变。

表达式reinterpret_cast<const T*>(data+pos)仍然指向类型为std::aligned_storage<...>::type的对象,而间接获取引用该对象的左值,尽管左值的类型为const T。对示例中表达式v1[0]的求值尝试通过左值访问std::aligned_storage<...>::type对象的值,这是根据[basic.lval]第11段(即严格别名规则)的未定义行为:

  

如果程序试图通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义:

     
      
  • 对象的动态类型

  •   
  • 对象动态类型的cv限定版本

  •   
  • 与对象的动态类型相似的类型(在[conv.qual]中定义),

  •   
  • 与对象的动态类型对应的有符号或无符号类型的类型,

  •   
  • 对应于对象动态类型的cv限定版本的有符号或无符号类型

  •   
  • 聚合或联合类型,包括其元素或非静态数据成员中的上述类型之一(包括递归地,子聚合或包含联合的元素或非静态数据成员),

  •   
  • 一种类型,它是对象动态类型的(可能是cv限定的)基类类型,

  •   
  • char,unsigned char或std :: byte类型。

  •   

答案 1 :(得分:0)

代码不以任何方式违反严格别名规则。类型const T的左值用于访问允许的T类型的对象。

有关规则,如相关问题所涵盖,是终身规则; C ++ 14(N4140)[basic.life] / 7。问题在于,根据此规则,指针data+pos可能不会用于操纵placement-new创建的对象。你应该使用价值&#34;返回&#34;通过placement-new。

问题自然如下:指针reinterpret_cast<T *>(data+pos)怎么样?目前还不清楚是否通过这个新指针访问新对象违反了[basic.life] / 7。

您链接到的答案的作者假定(没有提供任何理由)这个新指针仍然是指向原始对象的指针&#34;。然而,在我看来,也有可能认为,作为T *,它不能指向原始对象,即std::aligned_storage而不是T

这表明对象模型未指定。提案P0137已纳入C ++ 17,它正在解决对象模型不同部分的问题。但是它引入了std::launder这是一种 mjolnir 来压缩各种各样的别名,生命周期和起源问题。

毫无疑问,std::launder版本在C ++ 17中是正确的。但是,据我所知,P0137和C ++ 17还没有更多关于没有launder的版本是否正确的说法。

恕我直言,在没有std::launder的C ++ 14中调用代码UB是不切实际的,因为除了浪费内存存储放置的所有结果指针之外,没有办法解决问题。如果这是UB,那么在C ++ 14中实现std::vector是不可能的,这远非理想。