这个const_cast初始化是否具有未定义行为?

7

根据我的小测试,这段代码是可以工作的。但是,它是否具有未定义行为?通过使用const_cast修改const对象导致了我之前测试中的运行时访问冲突,但我不记得它们有什么不同了。所以,这里基本上有什么问题还是没有问题?

// test.h
#pragma once
#include <boost/array.hpp>

typedef boost::array<int,100000> bigLut_t;
extern const bigLut_t constBigLut;

// test.cpp
#include "test.h"

bigLut_t& initializeConstBigLut()
{
    bigLut_t* pBigLut = const_cast<bigLut_t*>( &constBigLut );

    for(int i = 0; i < 100000; ++i) {
        pBigLut->at(i) = i;
    }
    return const_cast<bigLut_t&>(constBigLut);
}

const bigLut_t constBigLut = initializeConstBigLut();

// const_test.cpp
#include <iostream>
#include "test.h"

void main()
{
    for(int i = 0; i < 100; ++i) {
        std::cout << constBigLut[i] << std::endl;
    }
    system("pause");
}

(请注意,sizeof(bigLut_t)太大而无法适合堆栈。)
编辑: 实际上,我最喜欢ybungalobill的小注释中的想法来初始化这些大对象:
// test.h
#pragma once
#include <boost/array.hpp>

extern const struct BigLut : public boost::array<int,100000> {
    BigLut();
} constBigLut;

// test.cpp
#include "test.h"

const BigLut constBigLut;
BigLut::BigLut()
{
    for(int i = 0; i < 100000; ++i) {
        this->at(i) = i;
    }
}

6
除此之外,在 C++ 中,void main 是非法的。main 函数必须始终具有返回类型 int。尽管如此,您可以安全地省略 return 语句。 - Konrad Rudolph
3个回答

6

您修改了一个被定义为const的对象。无论您何时进行修改,无论是在初始化期间还是其他时间,这都是未定义的行为。使用const_cast移除const属性仅在常量指针之前从非常量指针获得该对象时才被定义。这不是您的情况。

您能做的最好的事情是

const bigLut_t& initializeConstBigLut()
{
    static bigLut_t bigLot;

    for(int i = 0; i < 100000; ++i) {
        bigLut.at(i) = i;
    }
    return bigLut;
}

const bigLut_t constBigLut = initializeConstBigLut();

希望编译器能够优化掉静态临时变量。


3

您正在误用const_cast运算符,这是可行的,但在这种情况下会产生未定义的行为... 您可以通过调用其隐式复制构造函数(假设boost :: array与std :: array是相同的概念)使用动态初始化程序来初始化constBigLut

struct bigLut_tinit  {  
  bigLut_t BigLut; 

  bigLut_tinit() {
    for(int i = 0; i < 100000; ++i) {  
        BigLut[i] = i;  
    }
  }
};

const bigLut_tinit constBigLut;

编辑:看起来VC++10完美地应用了RVO,因此临时对象直接移动到静态持续对象中。所以我认为不需要声明本地静态或对临时变量的引用...
编辑2:是的,我忽略了大小问题。建议将其封装到具有上述构造函数的非平凡类型中...

OP 明确表示:"sizeof(bigLut_t) 太大了,无法放入堆栈中。" - Yakov Galka
1
+1 对于结构体的想法非常好。实际上,你可以做得更好:让这个结构体派生自 bigLut_t,那么使用 constBigLut 的代码将不需要改变。 - Yakov Galka
实际上,我根本不会这样做...静态持续时间的对象应该只是绝对特殊的紧急解决方案。从std::array派生会使其变得更加“hackier”,因为它显然违反了继承的目的。如果需要类似的东西,您可能会遇到某种架构问题,应该首先解决它。如果那行不通,将bigLut_tinit实例包装到一个安全的单例中,以便它不能被进一步滥用... - Paul Michalik
@paul_71:为了让我的应用程序正常工作,这些全局const对象必须始终存在,因此我看不到延迟它们的初始化的任何理由。此外,constBigLut的基类在概念上不是单例(也不一定是std::array)。这意味着我需要在代码的其他地方构造和销毁与某些全局const对象相同类型的对象。除了将它们视为架构问题之外,您能否给我提供其他原因,说明静态持续时间对象只应该是特殊紧急解决方案? - zeroes00
这就够了吗? :) 不,C++的静态变量存在几个固有问题。 - Paul Michalik
抱歉,他们不让我编辑评论,所以我从头开始...这样不够吗? :) 不,C++静态变量本身存在几个问题,比如无法(或很差地)控制它们的初始化和销毁(初始化/销毁顺序,它们是否被初始化等)。当然,还存在可测试性、多线程、引入不明显依赖关系等问题。我的经验是,任何依赖于C++静态变量的程序迟早会出问题... - Paul Michalik

1

这是一个 UB,因为该数组可以存储在 ROM 中。

你可以这样做:

// test.h
#include <boost/array.hpp>

typedef boost::array<int,100000> bigLut_t;
const bigLut_t& Lut();


// test.cpp
#include "test.h"

bool initialized=false;

const bigLut_t& Lut()
{
  static bigLut_t lut;

  if (!initialized)
  {
    for(int i = 0; i < 100000; ++i) {
        lut.at(i) = i;
    }
  }
    return lut;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接