C++ 在保证非常量分配的情况下丢弃非静态常量字段的常量是否合法

C++ 在保证非常量分配的情况下丢弃非静态常量字段的常量是否合法,c++,undefined-behavior,C++,Undefined Behavior,我有下面的代码,它似乎总是有效的(msvc、gcc和clang) 但我不确定这是否真的合法。在我的框架中,我的类可能有两个构造函数——一个普通的C++构造函数,它执行简单的成员初始化和一个附加的成员函数“cTor”,它执行附加的初始化代码。它用于允许例如对虚拟函数的调用。这些调用由一个通用的分配/构造函数处理,类似于“make_shared” 守则: #include <iostream> class Foo { public: constexpr Foo()

我有下面的代码,它似乎总是有效的(msvc、gcc和clang)

但我不确定这是否真的合法。在我的框架中,我的类可能有两个构造函数——一个普通的C++构造函数,它执行简单的成员初始化和一个附加的成员函数“cTor”,它执行附加的初始化代码。它用于允许例如对虚拟函数的调用。这些调用由一个通用的分配/构造函数处理,类似于“make_shared”

守则:

#include <iostream>

class Foo
{
    public:
      constexpr Foo() : someConstField(){}
    public:
        inline void Ctor(int i)
        {
            //use Ctor as real constructor to allow for example calls to virtual functions
            const_cast<int&>(this->someConstField) = i;
        }
    public:
      const int someConstField;
};

int main()
{
    //done by a generic allocation function
    Foo f;
    f.Ctor(12); //after this call someConstField is really const!

    //
    std::cout << f.someConstField;
}
#包括
福班
{
公众:
constexpr Foo():someConstField(){}
公众:
内联空分器(int i)
{
//使用Ctor作为实构造函数,以允许对虚拟函数进行示例调用
const_cast(this->someConstField)=i;
}
公众:
const int someConstField;
};
int main()
{
//由通用分配函数完成
福福;
f、 Ctor(12);//在这个调用之后,someConstField实际上是const!
//
标准::coutNo


修改
const
值是未定义的行为。
const\u cast
本身很好,问题在于修改。

修改
const
内存是未定义的行为。在这里,
int
已由默认构造函数分配到const内存中

老实说,我不知道你为什么要这么做。如果你想用
int
初始化
Foo
,只需创建一个重载构造函数:

。。。
constexpr Foo(inti):someConstField{i}{
这是完全合法的,在创建
常量时,您正在初始化它,一切正常

如果出于某种原因,您希望在两个阶段初始化对象(没有工厂函数不是一个好主意),那么您不能也不应该使用
const
成员变量。毕竟,如果在创建对象后可以更改,那么它将不再是const

一般来说,你不应该有
const
成员变量,因为它会导致很多问题,例如,移动一个对象


当我在这里说“const memory”时,我的意思是
const
根据语言的规则限定内存。因此,虽然内存本身在机器级别上可能是可写的,也可能不是可写的,但实际上并不重要,因为编译器会做任何它喜欢的事情(通常它只是忽略对该内存的任何写入,但这是UB,因此它可以做任何事情)。

根据C++17标准中的7.1.6.1

除了可以修改任何声明为可变(7.1.1)的类成员外,任何修改常量的尝试 对象在其生存期(3.8)内会导致未定义的行为

还有一个例子(与您的类似,但不包括班级成员):

const int*ciq=new const int(3);//根据需要初始化
int*iq=const_cast(ciq);//需要强制铸造
*iq=4;//未定义:修改常量对象

我建议您使用构造函数来避免常量强制转换。您评论说,在调用
Ctor
后,
someConstField
的值将保持常量。只需在构造函数中设置它,您就不会有问题,代码的可读性也会提高

#include <iostream>

class Foo
{
    public:
      constexpr Foo(int i) : someConstField(Ctor(i)){}

      int Ctor(); // to be defined in the implementation

      const int someConstField;
};

int main()
{
    Foo f(12);
    std::cout << f.someConstField;
}
#包括
福班
{
公众:
constexpr Foo(inti):someConstField(Ctor(i)){
int Ctor();//将在实现中定义
const int someConstField;
};
int main()
{
傅f(12),;

std::cout如果分配函数分配原始内存,则可以使用在该内存位置构造对象。这样,在释放分配之前,必须记住调用对象的析构函数

使用
malloc
的小示例:

class Foo
{
    public:
      constexpr Foo(int i) : someConstField(i){}
    public:
      const int someConstField;
};

int main()
{
    void *raw_memory = std::malloc(sizeof(Foo));
    Foo *foo = new (raw_memory) Foo{3}; // foo->someConstField == 3
    // ...
    foo->~Foo();
    std::free(foo);
}

这是否回答了你的问题?如果有一个单独的代码> ctot/c>在代码中使用一个通用的模式,你应该考虑分配函数不构造对象,使用布局<代码> new <代码>,而不是调用<代码> ctot/c> >方法。它分配对齐的非原始内存,放置新的,调用t。如果有的话,他就不工作了exists@Bernd是的,您可以执行
new(raw_memory)Foo{i};
,它返回指向构造对象的指针。请参阅上的Placement new部分。很好,您提供了一个示例,说明了在没有UB的情况下如何实现相同的功能。“const memory”的概念但是,这可能会产生误导。标准中没有此类概念,编译器也不一定会将数据放入只读内存。它真的是常量内存吗?@OMGtechy我认为这是一个相当好的抽象,因为它的实际只读内存与否通常是不相关的。你认为哪一个更好r如何描述它?@Firefly true。也许可以用编译器的假设来描述它-即编译器(尤其是优化程序)会假设您没有更改
常量
值,这个假设现在是错误的,因此可能会发生可怕的事情(UB).@Bernd它的const限定了语言方面的内存。这意味着它的未定义行为会在初始化后对其进行修改。在机器级别上写入该内存是否真的可以并不重要,因为它是UB,所以编译器可能会决定重写它以生成toast(实际上,它通常只忽略对它的任何写入)我实际上是用C++作为后端编写编译器。问题是,在源语言中,在构造函数中允许任意的计算和调用,所以我不能把代码翻译成这样简单的init表达式。@ Bernd Thats是一个单独的问题,但当然可以。只需调用初始化器中的函数:<代码> FoO(int i):someConstField{some_function(i)}{}
@Firefly或立即调用的lambda,您可以在其中放置任意代码,而不仅仅是函数
class Foo
{
    public:
      constexpr Foo(int i) : someConstField(i){}
    public:
      const int someConstField;
};

int main()
{
    void *raw_memory = std::malloc(sizeof(Foo));
    Foo *foo = new (raw_memory) Foo{3}; // foo->someConstField == 3
    // ...
    foo->~Foo();
    std::free(foo);
}