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

gojuced7  于 2022-12-05  发布在  其他
关注(0)|答案(4)|浏览(128)

如果不依赖于const_cast,当需要一个计算代价高昂的中间值来计算多个数据成员时,如何在 * 构造之后 * 而不是在 * 构造期间-使C++数据成员const
下面这个最小的、完整的、可验证的例子进一步解释了这个问题及其原因。为了避免浪费时间,我建议你从阅读这个例子的两个注解开始。

#include <iostream>

namespace {

    constexpr int initializer {3};
    constexpr int ka {10};
    constexpr int kb {25};

    class T {
    private:
        int value;
        const int a_;
        const int b_;
    public:
        T(int n);
        inline int operator()() const { return value; }
        inline int a() const { return a_; }
        inline int b() const { return b_; }
        int &operator--();
    };

    T::T(const int n): value {n - 1}, a_ {0}, b_ {0}
    {
        // The integer expensive
        //     + is to be computed only once and,
        //     + after the T object has been constructed,
        //       is not to be stored.
        // These requirements must be met without reliance
        // on the compiler's optimizer.
        const int expensive {n*n*n - 1};
        const_cast<int &>(a_) = ka*expensive;
        const_cast<int &>(b_) = kb*expensive;
    }

    int &T::operator--()
    {
        --value;
        // To alter a_ or b_ is forbidden.  Therefore, the compiler
        // must abort compilation if the next line is uncommented.
        //--a_; --b_;
        return value;
    }

}

int main()
{
    T t(initializer);
    std::cout << "before decrement, t() == " << t() << "\n";
    --t;
    std::cout << "after  decrement, t() == " << t() << "\n";
    std::cout << "t.a() == " << t.a() << "\n";
    std::cout << "t.b() == " << t.b() << "\n";
    return 0;
}

输出量:

before decrement, t() == 2
after  decrement, t() == 1
t.a() == 260
t.b() == 650

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

跟进

在阅读了几个答案并在我的PC上进行了实验之后,我认为我采取了错误的方法,因此提出了错误的问题。尽管C确实提供了const数据成员,但它们的使用往往与正常的数据范例相反。毕竟,什么是变量对象的const数据成员?它不是通常意义上的常量,是吗?因为你可以在它的父对象上使用=操作符来覆盖它。这是笨拙的。它不适合它的预期用途。
@Homer512的评论说明了我的方法的问题:
如果不方便的话,不要让自己过度紧张,使成员const。如果有的话,它可能会导致低效的代码生成,例如,使移动构造回落到复制构造。
防止无意中修改不应更改的数据成员的正确方法显然是不提供接口来更改它们-如果有必要保护数据成员不受类自己的成员函数的影响,为什么,@某个程序员的回答说明了如何做到这一点。
我现在怀疑是否有可能在C
中顺利地处理const数据成员。在这种情况下,const保护了错误的内容。

xa9qqrwz

xa9qqrwz1#

也许可以这样说:

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) {}
};
gz5pxeao

gz5pxeao2#

一种可能的方法是将ab放入第二个结构中,该结构进行昂贵的计算,然后具有该结构的常量成员。
也许是这样的:

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

也就是说,如果您控制类T及其实现,为什么首先要使a_b_保持不变呢?
如果您希望禁止其他开发人员对T类进行修改,那么可以添加大量文档和注解,说明不允许修改的值。这样,如果有人修改了a_b_的值,那就是他们的错了,因为他们做了一些可能会破坏性的修改。然后应该使用审查实践和适当的版本控制处理来指出并可能责备犯错者。

jq6vz3qz

jq6vz3qz3#

在描述答案之前,我首先建议您重新考虑您的接口。如果有一个开销很大的操作,为什么不让调用者知道它并允许他们缓存结果呢?通常,设计形式围绕着值得作为一种状态保留的计算和抽象;如果它价格昂贵且可重复使用,那么它绝对值得保留。
因此,我建议把这个放到公共接口上:

struct ExpensiveResult
{
    int expensive;

    ExpensiveResult(int n)
    : expensive(n*n*n - 1)
    {}
};

class T
{
private:
  const int a;
  const int b;

  T(const ExpensiveResult& e)
  : a(ka * e.expensive)
  , b(kb * e.expensive)
  {}
};

请注意,ExpensiveResult可以直接从int n构造(ctor不是explicit),因此当您不缓存它时,调用语法是类似的;但是调用者可以在任何时候开始存储昂贵计算的结果。

agyaoht7

agyaoht74#

由于c++20中的一个重大变化,修改对象中的常量非常容易。库函数construct_atdestroy_at已经提供来简化这一点。对于您的类,destroy_at是多余的,因为该类不包含使用动态内存的成员,如vector等。我做了一个小的修改,我添加了一个只接受int的构造函数。还定义了一个operator=,它允许在容器中操作对象。你也可以使用construct_at在你的operator--方法中递减a_和b_。代码如下:

#include <iostream>
    #include <memory>

    namespace {

        constexpr int initializer{ 3 };
        constexpr int ka{ 10 };
        constexpr int kb{ 25 };

        class T {
        private:
            int value;
            const int a_{};
            const int b_{};
        public:
            T(int n);
            T(int n, int a, int b);
            T(const T&) = default;
            inline int operator()() const { return value; }
            inline int a() const { return a_; }
            inline int b() const { return b_; }
            int& operator--();
            T& operator=(const T& arg) { std::construct_at(this, arg); return *this; };
        };

        T::T(const int n, const int a, const int b) : value{ n - 1 }, a_{ a }, b_{ b } {}
        T::T(const int n) : value{ n - 1 }
        {
            // The integer expensive
            //     + is to be computed only once and,
            //     + after the T object has been constructed,
            //       is not to be stored.
            // These requirements must be met without reliance
            // on the compiler's optimizer.
            const int expensive{ n * n * n - 1 };
            std::construct_at(this, n, ka*expensive, kb*expensive);
        }

    int& T::operator--()
    {
        // implement decrements
        //--a_; --b_;
        const int a_1 = a_ - 1;
        const int b_1 = b_ - 1;
        std::construct_at(this, value, a_1, b_1);
        return value;
    }
}

int main()
{
    T t(initializer);
    std::cout << "before decrement, t() == " << t() << "\n";
    --t;
    std::cout << "after  decrement, t() == " << t() << "\n";
    std::cout << "t.a() == " << t.a() << "\n";
    std::cout << "t.b() == " << t.b() << "\n";
    return 0;
}

输出量:

before decrement, t() == 2
after  decrement, t() == 1
t.a() == 259
t.b() == 649

相关问题