从指向一个对齐结构的指针转换为另一个具有相同“前缀”成员的指针是否合法?

kof*_*fes 7 c++ smart-pointers memory-alignment language-lawyer c++17

我试图创建一个智能指针,它只有一个指向内存块的指针,该指针以引用计数器(控制块)开头,并在其后立即存储一个值。在从论坛、标准和 cppreference 中阅读了一些内容后,我意识到代码看起来充满了 UB。

  1. 那么,这段代码相对于 C++17 标准是否合法?
#include <iostream>

#include <memory>
#include <type_traits>
#include <new>

struct alignas(alignof(size_t)) StorageBase {
    size_t m_rc = 0;
};

template<typename T>
struct Storage: public StorageBase {
    using value_type = T;

    std::aligned_storage_t<sizeof(value_type), alignof(value_type)> m_value_storage;
};

class Dummy {
public:
    Dummy() { std:: cout << "Dummy constructed" << std::endl; }
    virtual ~Dummy() { std:: cout << "Dummy destructed" << std::endl; }
};

class DerivedDummy: public Dummy {
public:
    DerivedDummy() { std:: cout << "DerivedDummy constructed" << std::endl; }

    ~DerivedDummy() override { std:: cout << "DerivedDummy destructed" << std::endl; }
};

int main(int argc, char const *argv[]) {
    using first_storage_type = Storage<DerivedDummy>;

    auto* first_storage = new first_storage_type;
    ++first_storage->m_rc;
    new (&first_storage->m_value_storage) first_storage_type::value_type();

    StorageBase* storage = first_storage;

    using second_storage_type = Storage<Dummy>;

    if constexpr (std::is_convertible_v<first_storage_type::value_type*, second_storage_type::value_type*>) {
        using second_value_type = second_storage_type::value_type;

        // UB ?
        auto* second_storage = static_cast<second_storage_type*>(storage);

        ++second_storage->m_rc;

        // UB ?
        std::launder<second_value_type>(
            static_cast<second_value_type*>(
                static_cast<void*>(
                    &second_storage->m_value_storage
                )
            )
        )->~second_value_type();

        delete second_storage;
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

因为我知道存在解决方案,您可以在其中添加指向StorageBase类的指针aligned_storage<...>::type,它会正常工作。

  1. 但是,有没有没有开销的解决方案,只使用对齐并符合标准?

第二次尝试

在阅读了有关对齐和标准布局的更多信息后,我将代码重构为新的代码。

  1. 合法吗?
#include <iostream>

#include <memory>
#include <type_traits>
#include <new>

// explicit alignment here...
struct alignas(size_t) StorageBase {
    size_t m_rc = 0;
};

template<typename T>
// ... and here
struct alignas(StorageBase) Storage: public StorageBase {
    using value_type = T;

    std::aligned_storage_t<sizeof(value_type), alignof(value_type)> m_value_storage;
};

class Dummy {
public:
    Dummy() { std:: cout << "Dummy constructed" << std::endl; }
    virtual ~Dummy() { std:: cout << "Dummy destructed" << std::endl; }
};

class DerivedDummy: public Dummy {
public:
    DerivedDummy() { std:: cout << "DerivedDummy constructed" << std::endl; }

    ~DerivedDummy() override { std:: cout << "DerivedDummy destructed" << std::endl; }
};

int main(int argc, char const *argv[]) {
    using first_storage_type = Storage<DerivedDummy>;

    auto* first_storage = new first_storage_type;
    ++first_storage->m_rc;

    const auto ptr_to_storage = new (&first_storage->m_value_storage) first_storage_type::value_type();

    StorageBase* storage = first_storage;

    using second_storage_type = Storage<Dummy>;

    if constexpr (std::is_convertible_v<first_storage_type::value_type*, second_storage_type::value_type*>) {
        using second_value_type = second_storage_type::value_type;

        /*
         * Storage<T> is explicitly aligned as StorageBase,
         * but Storage<T> is not standard layout
        */
        second_value_type* ptr = reinterpret_cast<second_value_type*>(storage + 1); // UB?

        ++storage->m_rc;

        std::launder<second_value_type>(ptr)->~second_value_type();

        ::operator delete(storage);
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)