如何在构造之后而不是构造期间使数据成员成为常量?

thb*_*thb 0 c++ initialization constants

在不依赖的情况下const_cast,如何const 在之后而不是期间创建一个 C++ 数据成员,当计算多个数据成员需要一个计算成本高昂的中间值时,

\n

下面的最小、完整、可验证的示例进一步解释了这个问题及其原因。为了避免浪费您的时间,我建议您首先阅读示例的两条注释。

\n
#include <iostream>\n\nnamespace {\n\n    constexpr int initializer {3};\n    constexpr int ka {10};\n    constexpr int kb {25};\n\n    class T {\n    private:\n        int value;\n        const int a_;\n        const int b_;\n    public:\n        T(int n);\n        inline int operator()() const { return value; }\n        inline int a() const { return a_; }\n        inline int b() const { return b_; }\n        int &operator--();\n    };\n\n    T::T(const int n): value {n - 1}, a_ {0}, b_ {0}\n    {\n        // The integer expensive\n        //     + is to be computed only once and,\n        //     + after the T object has been constructed,\n        //       is not to be stored.\n        // These requirements must be met without reliance\n        // on the compiler\'s optimizer.\n        const int expensive {n*n*n - 1};\n        const_cast<int &>(a_) = ka*expensive;\n        const_cast<int &>(b_) = kb*expensive;\n    }\n\n    int &T::operator--()\n    {\n        --value;\n        // To alter a_ or b_ is forbidden.  Therefore, the compiler\n        // must abort compilation if the next line is uncommented.\n        //--a_; --b_;\n        return value;\n    }\n\n}\n\nint main()\n{\n    T t(initializer);\n    std::cout << "before decrement, t() == " << t() << "\\n";\n    --t;\n    std::cout << "after  decrement, t() == " << t() << "\\n";\n    std::cout << "t.a() == " << t.a() << "\\n";\n    std::cout << "t.b() == " << t.b() << "\\n";\n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

输出:

\n
before decrement, t() == 2\nafter  decrement, t() == 1\nt.a() == 260\nt.b() == 650\n
Run Code Online (Sandbox Code Playgroud)\n

(我知道之前的初学者问题,但它处理的是基本情况。请参阅上面代码中的注释。我的问题是我有一个昂贵的初始化,我不想执行两次,其中间结果我不希望存储;而我仍然希望编译器在构造完成后保护我的常量数据成员。我意识到一些 C++ 程序员原则上避免常量数据成员,但这是一个风格问题。我不是问如何避免使用常量数据成员;我问的是如何在像我这样的情况下实现它们而不求助于const_cast它们,而不求助于也不浪费内存、执行时间或运行时电池电量。)

\n

跟进

\n

在阅读了几个答案并在我的电脑上进行实验后,我相信我采取了错误的方法,因此提出了错误的问题。尽管 C++ 确实提供了const数据成员,但它们的使用往往与正常的数据范例相反。变量对象的数据成员到底什么?const它并不是通常意义上的常数,是吗?因为人们可以使用=在其父对象上使用运算符来覆盖它。这很尴尬。它不符合其预期目的。

\n

@Homer512\'s 的评论说明了我的方法的问题:

\n
\n

不要给自己太大压力去培养会员const在不方便的时候给自己太大的压力去结交会员。如果有的话,它可能会导致低效的代码生成,例如通过使移动构造回退到复制构造。

\n
\n

显然,防止无意中修改不应更改的数据成员的正确方法是,简单地不提供任何接口来更改它们\xe2\x80\x94,并且如果有必要保护数据成员免受类自己的成员函数的影响,为什么,@Some程序员老兄的回答展示了如何做到这一点。

\n

我现在怀疑是否可以const在 C++ 中顺利地处理数据成员。这const保护了错误的东西。

\n

Igo*_*nik 8

也许是这样的:

class T {
private:
  T(int n, int expensive)
    : value{n-1}, a_{ka*expensive}, b_{kb*expensive} {}
public:
  T(int n) : T(n, n*n*n - 1) {}
};
Run Code Online (Sandbox Code Playgroud)

  • 进一步研究的搜索词是[委托构造函数](https://en.cppreference.com/w/cpp/language/constructor#Delegating_constructor) (3认同)

Som*_*ude 6

一种可能的方法是将aand放入b第二个结构中,该结构进行昂贵的计算,然后拥有该结构的常量成员。

也许是这样的:

class T {
    struct constants {
        int a;
        int b;

        constants(int n) {
            const int expensive = ... something involving n...;
            a = ka * expensive;
            b = kb * expensive;
        }
    };

    constants const c_;

public:
    T(int n)
        : c_{ n }
    {
    }
};
Run Code Online (Sandbox Code Playgroud)

话虽如此,如果您控制了类及其实现,为什么首先要使用makea_和constant呢?b_T

如果您想禁止可能参与该类的其他开发人员进行可能的修改T,请添加大量有关不允许修改的值的文档和注释。a_然后,如果有人修改了或 的值b_,那么这是他们做出可能的破坏性更改的错误。然后应该使用良好的代码审查实践和正确的版本控制处理来指出并可能责备不当行为者。