看《C++ Functional Programming》的时候遇到一种新的new的用法

template<typename T, typename E>
class expected {
private:
    union {
        T m_value;
        E m_error;
    };
    template <typename ...Args>
    static expected sucess(Args &&...params)
    {
        expected result;
        result.m_valid = true;
        new (&result.m_value)
            T(std::forward<Args>(params)...);
        return result;
    }
};

找文档学习了一下,以下是记录

布局(placement)new

如果提供了布局参数(placement-params),它们将作为附加参数传递给分配函数(allocation function)。这样的分配函数被称为 “placement new”,在标准分配函数void* operator new(std::size_t, void*)之后,它只是简单地返回其第二个参数而不改变。这被用来在分配的存储中构造对象。例如:

{
    // 静态分配足够大的空间,并且进行地址对齐
    alignas(T) unsigned char buf[sizeof(T)];
    // 构建一个T对象,将其直接放入预先分配好的内存地址buf的存储空间。
    T *tptr = new(buf) T;
    // 必须要手动调用对象的析构函数,如果它的副作用是由程序决定的。让该内存块自动解除分配buf
    tptr->~();
}

当分配一个对齐要求超过__STDCPP_DEFAULT_NEW_ALIGNMENT__的对象或这样的对象的数组时,new-expression 将对齐要求(用std::align_val_t封装)作为分配函数的第二个参数传递(对于放置形式,放置参数出现在对齐之后,作为第三、第四等参数)。如果重载解析失败(当一个特定于类的分配函数被定义为不同的签名时,这种情况就会发生,因为它隐藏了globals),重载解析会被第二次尝试,参数列表中没有对齐。这允许无对齐意识的特定类分配函数优先于全局对齐意识的分配函数。

不同new操作实际的参数

new T;      // call       operator new(sizeof(T))
            // (C++17)    operator new(sizeof(T), std::align_val_t(alignof(T)))
new T[5];   // call       operator new[](sizeof(T)*5 + overhead)
            // (C++17)    operator new(sizeof(T)*5 + overhead, std::align_val_t(alignof(T)))
new (2,f) T;// call       operator new(sizeof(T), 2, f)
            // (C++17)    operator new(sizeof(T), std::align_val_t(alignof(T)), 2, f)

如果一个非抛出的分配函数(例如new(std::nothrow) T所选择的函数)因为分配失败而返回一个空指针,那么新表达式立即返回,它不会试图初始化一个对象或调用一个去分配函数。如果一个空指针被作为参数传递给一个非分配布局的新表达式,这使得选定的标准非分配布局分配函数返回一个空指针,那么行为是未定义的。

内存对齐

对齐方式

不同内置变量默认的对齐方式不一样,比如对齐为4,其地址指针能够被4整除。可以增加数据寻址的效率

#include <string>
#include <algorithm>
#include <iostream>

struct Test
{
    char a[1];
    short b;
    int c;
    double d;
};

int main()
{
    Test t;;
    std::cout << sizeof(Test) << " " << alignof(Test) << std::endl;
    std::cout << "a: " << &t.a << " " << alignof(char) << " " << __builtin_offsetof(Test, a) << std::endl;
    std::cout << "b: " << &t.b << " " << alignof(short) << " " << __builtin_offsetof(Test, b)  << std::endl;
    std::cout << "c: " << &t.c << " " << alignof(int) << " " << __builtin_offsetof(Test, c)  << std::endl;
    std::cout << "d: " << &t.d << " " << alignof(double) << " " << __builtin_offsetof(Test, d)  << std::endl;
}

结果:

16 8
a: 0x7ffccd6cee60 1 0
b: 0x7ffccd6cee62 2 2
c: 0x7ffccd6cee64 4 4
d: 0x7ffccd6cee68 8 8

结构体中的每种变量的地址都按照自己的对齐方式进行地址对齐,结构体的对齐方式默认使用成员中最大的对齐方式

改变对齐

使用alignas改变对齐方式,例如

struct alignas(32) Test
{
    char a[1];
    short b;
    int c;
    double d;
};

但是即使设置alignas,也可能失效,比如对于Test设置为4,结果为8

对齐必须为2的2的正数幂

std::aligned_storage可以用来获取指定对齐方式的一块空间,其实现如下:

template<std::size_t Len, std::size_t Align = /* default alignment not implemented */>
struct aligned_storage {
    struct type {
        alignas(Align) unsigned char data[Len];
    };
};

对于已经有的空间需要取一块对齐的指定大小的内存,则使用std::align,实现如下:

// _Bound: 需要的对齐方式
// _Size: 对齐的存储大小
// _Ptr: 指针指向至少有空间字节的连续存储(缓冲区)。
// _Space: 操作的缓冲区的大小
inline void* align(size_t _Bound, size_t _Size, void*& _Ptr, size_t& _Space) noexcept /* strengthened */ {
    // try to carve out _Size bytes on boundary _Bound
    size_t _Off = static_cast<size_t>(reinterpret_cast<uintptr_t>(_Ptr) & (_Bound - 1));
    if (_Off != 0) {
        _Off = _Bound - _Off; // number of bytes to skip
    }

    if (_Space < _Off || _Space - _Off < _Size) {
        return nullptr;
    }

    // enough room, update
    _Ptr = static_cast<char*>(_Ptr) + _Off;
    _Space -= _Off;
    return _Ptr;
}

链接