3

下面的类是否打破了严格的别名规则:

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标准中放置的示例。

4

2 回答 2

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且对齐方式是Align的除数的对象的未初始化存储。

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

我相信,只要您不直接访问底层存储,别名规则甚至都不会出现,因为别名规则涵盖了何时可以通过不同类型的对象访问对象的值. 构造一个对象并仅访问该对象并不涉及通过任何其他类型的对象访问该对象的值。

较早的答案

别名规则特别允许 char 数组对其他对象进行别名。

如果程序尝试通过非下列类型之一的泛左值访问对象的存储值,则行为未定义:

[...]

— 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 数组的值。但是,如果placement new 表达式定义良好,那么它会创建一个T 对象,我认为可以按照定义将其作为T 对象访问,并且读取原始char 数组正在访问创建的T 对象的值,这将在下面介绍别名规则。

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

于 2012-06-09T23:38:04.483 回答
0

允许的是获取一个T对象并将其解释为一个字符数组。但是,通常不允许获取任意字符数组并将其视为 a T,甚至是指向包含 a 的内存区域的指针T。至少,您的 char 数组需要正确对齐。

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

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

现在你可以构造一个Tinside storage.buf

T * p = ::new (storage.buf) T();
于 2012-06-09T23:40:35.740 回答