封装的char数组用作对象是否会破坏严格的别名规则

时间:2012-06-09 23:29:08

标签: c++ placement-new reinterpret-cast strict-aliasing explicit-destructor-call

以下类是否违反严格别名规则:

template<typename T>
class store {
    char m_data[sizeof(T)];
    bool m_init;
public:
    store() : m_init(false) {}
    store(const T &t) : init(true) {
        new(m_data) T(t);
    }
    ~store() {
        if(m_init) {
            get()->~T();
        }
    }
    store &operator=(const store &s) {
        if(m_init) {
            get()->~T();
        }
        if(s.m_init) {
            new(m_data) T(*s.get());
        }
        m_init = s.m_init;
    }
    T *get() {
        if (m_init) {
            return reinterpret_cast<T *>(m_data);
        } else {
            return NULL;
        }
    }
}

我对标准的解读是它不正确但我不确定(我的用法是拥有一个对象数组T +这些对象的一些元数据,但是可以控制对象构造/解构而不用手动分配内存),因为分配的对象用作标准中放置new的示例。

2 个答案:

答案 0 :(得分:4)

该标准包含以下注释:

  

[注意:典型的实现会将aligned_storage定义为:

template <std::size_t Len, std::size_t Alignment>
struct aligned_storage {
  typedef struct {
    alignas(Alignment) unsigned char __data[Len];
  } type;
};
     

- 结束记录]

     

- 指针修改[meta.trans.ptr] 20.9.7.5/1

且aligned_storage的部分定义为:

  

成员typedef 类型应为POD类型,适合用作任何大小最多为 Len 并且其对齐方式为对齐

标准所涵盖的唯一属性是限制可以构造对象的地址。实现可能有一些其他限制,但我不熟悉任何其他限制。因此,只需确保正确对齐就足够了,我认为这应该没问题。 (在预C ++ 11编译器中,您可以使用编译器扩展来设置对齐,例如__attribute__((alignment(X)))__declspec(align(X))

我相信只要你不直接访问底层存储,别名规则就不会出现在图片中,因为别名规则涵盖了什么时候可以通过一个对象来访问一个对象的值。不同的类型。构造对象并仅访问该对象不涉及通过任何其他类型的对象访问对象的值。

早期答案

别名规则特别允许char数组为其他对象添加别名。

  

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

     

[...]

     

- char或unsigned char类型。

     

- 左值和右值[basic.lval] 3.10 / 10

您确实需要确保数组正确对齐T类型。

alignas(T) char m_data[sizeof(T)];

以上是用于设置对齐的C ++ 11语法,但如果您使用的是C ++ 03编译器,那么您将需要一个特定于编译器的属性来执行相同的操作。 GCC有__attribute__((aligned(32))),MSVC有__declspec(align(32))


Kerrek SB提出了一个很好的观点,即别名规则声明可以通过char数组访问T对象的值,但这可能并不意味着通过T对象访问char数组的值是可以的。但是,如果放置新表达式定义良好,那么创建一个T对象,我认为可以按定义访问作为T对象,并且读取原始char数组正在访问创建的T对象的值,该对象包含在别名规则。

我认为这意味着您可以将T对象存储在例如int数组中,并且只要您不通过原始int数组访问该T对象的值,那么您就不会遇到未定义的行为。

答案 1 :(得分:0)

允许使用T对象并将其解释为字符数组。但是,通常 not 允许使用任意数组的字符并将其视为T,或者甚至作为指向包含T的内存区域的指针。至少,你的char数组需要正确对齐。

解决这个问题的一种方法可能是使用联合:

union storage { char buf[sizeof(T)]; T dummy; };

现在,您可以在T内构建storage.buf

T * p = ::new (storage.buf) T();