如何默认初始化非POD对象到同一地址?

问题描述 投票:0回答:1

tl;博士;如何使对象的每个实例都具有相同的常量地址,然后在用户想要更改它后对其进行分配?

我需要开发自己的 NULL (ish) 对象,并且希望默认初始化为 NullFoo (在值和地址中),因为每次构造对象时它只会生成 NullFoo 但在不同的地址上。

在上下文中,空(ish)值并不重要,它形成序列化的信息管理(即它未序列化)。让每个“未填充”数据结构从某处开始而不浪费空间,在动态内存管理中仍然非常有用。

所以请考虑下面的示例代码:

//pretty lights
#define CLOG(x) std::clog <<  "\033[48;2;100;150;100m" << "created\t" << x << "\033[m\n";
#define cLOG(x) std::clog <<  "\033[48;2;100;150;100m" << "copied\t" << x << "\033[m\n";
#define rLOG(x) std::clog <<  "\033[48;2;100;150;150m" << "reseted\t" << x << "\033[m\n";
#define dLOG(x) std::clog <<  "\033[48;2;150;100;100m" << "deleted\t" << x << "\033[m\n";
#define aLOG(x) std::clog <<  "\033[48;2;100;130;100m" << "assigned\t" << x << "\033[m\n";
#define caLOG(x) std::clog <<  "\033[48;2;100;130;120m" << "copy assigned\t" << x << "\033[m\n";

struct foo{
    int objdata;
    
    foo()
    {
        this->objdata = 0;
        CLOG(*this);
    }
    foo(const foo &other)
    {
        this->objdata = other.objdata;
        cLOG(*this);
    }
    foo(foo &&other)
    {
        this->objdata = other.objdata;
        cLOG(*this);
        other.objdata =0;
        rLOG(other);
    }
    ~foo()
    {
         dLOG(*this)
    };
    
    foo &operator=(int value)
    { 
        this->objdata = value;
        aLOG(*this);
        return *this;
    }
    foo &operator=(const foo &other)
    { 
        this->objdata =other.objdata;
        caLOG(*this);
        return *this;
    }
    foo &operator=(foo &&other)
    { 
        this->objdata = other.objdata;
        caLOG(*this);
        other.objdata = 0;
        rLOG(other);
        return *this;
    }

    //required for pre-processor shenanigans (just visual que)
    friend std::ostream &operator<<(std::ostream &, const foo &);
};


const foo NullFoo;

std::ostream &operator<<(std::ostream &os, const foo &f){
    os << f.objdata<< '\t' << &f << '\t' << &NullFoo;
    return os;
}

和入口点:

int main(){
    { // scope so pretty lights show
    std::cout << NullFoo << std::endl;
    foo a;
    std::cout << a << std::endl;
    a = rand()%0x100;
    std::cout << a << std::endl;
    }

    return 1;
}

输出:

created 0   0x5600e97b13ec  0x5600e97b13ec
0   0x5600e97b13ec  0x5600e97b13ec
created 0   0x7fff9ae33454  0x5600e97b13ec
0   0x7fff9ae33454  0x5600e97b13ec
assigned    103 0x7fff9ae33454  0x5600e97b13ec
103 0x7fff9ae33454  0x5600e97b13ec
deleted 103 0x7fff9ae33454  0x5600e97b13ec
deleted 0   0x5600e97b13ec  0x5600e97b13ec

如您所见,eac 初始化在新地址上,随后被销毁。

NullFoo
0x5600e97b13ec
上创建,每个后续构造函数都会创建一个新地址(在本例中为
0x7fff9ae33454
)。似乎内存是在调用构造函数之前分配的(但我不知道如何检查)。

如何使对象的每个实例都具有相同的常量地址,然后在用户想要更改它后分配它?

正在开发

Ubuntu 22.04
LTS、
g++
11.4.0.

c++ memory null constants dynamic-memory-allocation
1个回答
0
投票

两个对象不能共享相同的地址。并且一个对象不能改变地址。

要从相同的对象开始并通过突变进行更改......这就是引用语义,并且您可以使用评论中描述的(原始或智能)指针类型来获得它。

例如,

const foo NullFoo;
// becomes -->
const std::shared_ptr<const foo> NullFoo;
// or
const foo NullFooObj;
const foo* const NullFoo = &NullFooObj;

foo a;
// becomes -->
std::shared_ptr<const foo> a = NullFoo;
// or
const foo* a = NullFoo;

a = rand()%0x100;
// becomes -->
a = std::make_shared<const foo>(rand()%0x100);
// or
foo aobj = rand()%0x100;
a = &aobj;

可以将其封装在包装类型中。使用原始指针需要一个具有兼容生命周期的对象,而shared_ptr不需要,但需要堆。原始指针通常太不灵活,无法用于通用 API,因为调用者必须确保生命周期,这意味着所有工作必须一次性完成(无队列、异步)。因此,在地址(对象标识)很重要的情况下,您可能会看到共享指针和所需的堆分配。

在上下文中,空(ish)值并不重要,它形成序列化的信息管理(即它未序列化)。让每个“未填充”数据结构从某处开始而不浪费空间,在动态内存管理中仍然非常有用。

您所要求的东西不适合此用例。在问题的代码中,没有发生内存分配或堆使用。每个 foo 要么是静态对象,要么在堆栈上。尝试通过特定地址识别空对象会迫使内存管理策略远远不太理想。

解决此问题的更有效方法是使用可以表示无效性的类型:

foo a;
// becomes -->
std::optional<foo> a;

// this is unchanged
a = rand()%0x100;

确实,从技术上讲,堆栈上有用于保存 foo 的空间。然而,分配比堆分配便宜几个数量级。

或者,可以根据某些类型属性决定不进行序列化:例如不要序列化等于相同类型的值初始化对象的值,或者不要序列化等于特征类型指定的值的值。

© www.soinside.com 2019 - 2024. All rights reserved.