简体   繁体   English

避免 C++14 中的字段零初始化

[英]Avoid field zero initialization in C++14

Since C++14 (it might be C++11, i'm not sure), zero initialization happens on class construction on certain conditions, depending on the way the constructor is called.由于 C++14(可能是 C++11,我不确定),在某些条件下,类构造会发生零初始化,具体取决于调用构造函数的方式。 Is there a way to ensure a raw value field (let's say a pointer) is never zero initialized ?有没有办法确保原始值字段(比方说指针)永远不会被初始化为零?

I guess not because it seems that zero initialization happens at the class level and not at the field level (surely a kind of memset(this, 0, sizeof(TheClass)) ), but I'm still hoping there is way, a hack, something...我猜不是因为似乎零初始化发生在类级别而不是字段级别(肯定是一种memset(this, 0, sizeof(TheClass)) ),但我仍然希望有办法,一个黑客, 某物...

The idea is to be able to initialize a field before a placement new is called so that that member is available during construction time.其想法是能够在调用放置 new 之前初始化一个字段,以便该成员在构建期间可用。

According to cppreference's take on zero initialization :根据 cppreference 对 零初始化的看法:

If T is an non-union class type, all base classes and non-static data members are zero-initialized, and all padding is initialized to zero bits.如果 T 是非联合类类型,则所有基类和非静态数据成员都初始化为零,并且所有填充都初始化为零位。 The constructors, if any, are ignored.构造函数(如果有)将被忽略。

If your object is a victim of zero initialization you're out of luck.如果您的对象是零初始化的受害者,那么您就不走运了。 And every object with static or thread local storage duration will always be zero initialized (except for constant initialization).并且每个具有静态或线程本地存储持续时间的对象将始终为零初始化(常量初始化除外)。

Is there a way to ensure a raw value field (let's say a pointer) is never zero initialized ?有没有办法确保原始值字段(比方说指针)永远不会被初始化为零?

It is possible, yes.这是可能的,是的。

If the class is trivially default constructible, then simply default initialise the object, and its members will be default initialised as well:如果该类可以简单地默认构造,则只需默认初始化对象,其成员也将默认初始化:

int main() {
    T t; // default initialised

If the class is not trivially default constructible, you can write a user defined constructor that leaves the member uninitialised (note that doing this is generally discouraged):如果该类不是普通的默认可构造函数,您可以编写一个用户定义的构造函数,使成员未初始化(请注意,通常不鼓励这样做):

struct S {
    int member;
    S(){} // member is left default initialised

Warning: With a user defined constructor, the member would be left default initialised even if the object is value initialised.警告:使用用户定义的构造函数,即使对象是值初始化的,成员也会被默认初始化。


Objects with static storage duration are always zero or constant initialised.具有静态存储持续时间的对象始终为零或常量初始化。 Neither of those leave members uninitialised.这些都不会使成员未初始化。 Only way to avoid that initialisation is to not create objects with static storage duration.避免初始化的唯一方法是不创建具有静态存储持续时间的对象。


The idea is to be able to initialize a field before a placement new is called so that that member is available during construction time.其想法是能够在调用放置 new 之前初始化一个字段,以便该成员在构建期间可用。

If you mean your idea is to initialise a field of an object that hasn't yet been created with placement new, then the idea is not possible to implement.如果你的意思是你的想法是初始化一个尚未使用placement new 创建的对象的字段,那么这个想法是不可能实现的。

Members are available during construction time.成员在施工时间可用。 You don't need any tricks to achieve this.你不需要任何技巧来实现这一目标。 All members that can be used by their siblings in order of their initialisation.可以由其兄弟姐妹按照初始化顺序使用的所有成员。 And the constructor body is run after all members have been initialised.并且在所有成员都初始化后运行构造函数体。

As @eerorika's answer suggests:正如@eerorika 的回答所暗示的那样:

struct S {
    int member;
    S(){} // member is left default initialised

...note that doing this is generally discouraged ...请注意,通常不鼓励这样做

I would say that the clearest way to achieve this without confusing the user of the class would be not to use a int , but a std::aligned_storage_t as it is clear that this member is used just for storage of objects that might be created later rather than a value that can be used on its own.我会说,在不混淆类用户的情况下实现这一目标的最清晰方法不是使用int ,而是使用std::aligned_storage_t ,因为很明显该成员仅用于存储可能稍后创建的对象而不是可以单独使用的值。

Depending how much code you would like to write, this might be tedious, because you have to write placement new s and use std::launder (since c++17) to be compliant with the standard.根据您想要编写多少代码,这可能会很乏味,因为您必须编写放置 new并使用std::launder (c++17 起)才能符合标准。 However, this is IMO the best way to express your intent.但是,这是 IMO 表达您意图的最佳方式。

cppreference gives agood example of how to use std::aligned_storage (with some modification, see comments): cppreference 给出了如何使用std::aligned_storage一个很好的例子(有一些修改,见评论):

#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:
    // My modification here suppresses zero-initialization
    // if initialized with empty braces
    static_vector() {};

    // 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{};

        // construct value in memory of aligned storage
        // using inplace operator new
        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 
    {
        // note: needs std::launder as of C++17
        return *reinterpret_cast<const T*>(&data[pos]);
    }

    // Delete objects from aligned storage
    ~static_vector() 
    {
        for(std::size_t pos = 0; pos < m_size; ++pos) {
            // note: needs std::launder as of C++17
            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';

    static_vector<std::size_t, 10> v2{};

    // This is undefined behavior.
    // Here it's just used to demonstrate that
    // the memory is not initialized.
    std::cout << v2[0] << "\n";
}

Run it on compiler explorer在编译器资源管理器上运行它


However, since the c++ standard only mandates when zero-initialization must happen, but not when it must not happen, sometimes you really have to wrestle with your compiler in order to suppress zero initialization.然而,由于 c++ 标准只规定必须发生零初始化的时候,而不是必须发生零初始化的时候,有时你真的必须与你的编译器搏斗以抑制零初始化。

See this question and this question for more details.有关更多详细信息,请参阅此问题此问题

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM