【问题标题】:const * const * member variable initialization c++const * const * 成员变量初始化 C++
【发布时间】:2013-05-22 09:31:38
【问题描述】:

我在初始化这个结构时遇到问题(例如简化)

struct S{ const float * const * const data;};

基本上我有一个浮点缓冲区的缓冲区,我使用 const 来确保使用 S 的人不能对此成员进行任何更改(只读)。

我的问题是这很复杂且难以阅读以初始化,我想使用一个返回 const S 的 lambda,因此我可以通过写入成员名称来初始化我的 lambda 中的成员:s.data = ptr;

现在这段代码很复杂,我想知道什么是更好的解决方案。

AFAIK,拥有struct S{float ** data;}const S 无法有效保护成员的内容,我无法修改S::data,但我可以修改*S::data

我该怎么办?

谢谢

【问题讨论】:

  • 无论你做什么,s.data = ptr; 行都不会编译,因为dataconst 并且不能分配给它。如果你想这样做,你必须改变你的设计。
  • 是的,目前我有一个特定的 CTor,但我希望有非 const 成员我喜欢这样:herbsutter.com/2013/04/05/…
  • 这个问题太棒了:-) ... +1
  • 谢谢,但我更想知道我很笨,有一个简单的解决方案,但我在3天后没有找到它
  • 为什么不使用struct S { const float *const *data; };?这会让你很容易初始化S,但是你可以传递一个const S *...

标签: c++ c++11


【解决方案1】:

为什么不直接删除最后一个const

struct S{ const float * const * data;};

这样你可以随意初始化data,但它仍然不能用于修改它指向的任何东西

data 本身可以修改,但如果要阻止,它应该只是private

【讨论】:

  • 不,因为在这种情况下,这个结构的用户(我想保护它)可以自己更改数据,然后 data 中的所有缓冲区和 data[x] 中的所有缓冲区都会泄漏。我同意你的解决方案,将数据设为私有,并添加一堆 getter 函数,但如果我必须达到那个极限,我更喜欢在构造函数中简单地初始化它。我真正的问题是如何使界面更清晰,也许使用 lambda 来初始化我的 const 成员。
  • @dzada:那么你可以轻松更改data,但你不希望其他人也能这样做,嗯?您有两个选择:1)将其设为私有,可能在子类中; 2) 将其设为 const 并以某种方式对其进行初始化(但您已经知道了)。
  • 是的,我想让创建结构变得简单明了。 (我可以使用显式 ctor,初始化列表,但它不像 lambda 中的 struct.member = xxx 那样清晰)但是一旦我的结构被构建并传递给一个函数,例如,我想让它不可能修改任何一个数据数据[x] 或数据[x][y]。我在这里修改了我的答案,因为重读它,我认为这可能是最好的方法。大家怎么看?
【解决方案2】:

推荐的方法是向S 添加一个构造函数。这允许您在 ctor 初始化器列表中设置数据的值。

struct S
{
    explicit S(const float *const *const d) : data(d) {}
    const float * const * const data;
};

S GetS()
{
    float **data = GetData();

    return S(data);
}

如果你想限制谁可以在S::data 初始化后更改它,你可以 成员变量并使用友谊来允许访问。这需要将data 成员封装在提供转换和赋值运算符的附加结构中。

struct Outer
{
    struct S
    {
    private:

        struct ConstBox
        {
            friend Outer;

            ConstBox(const ConstBox& other) : data_(other.data_) {}
            explicit ConstBox(const float *const *const data) : data_(data) {}
            operator const float* const* () const { return data_; }

        private:

            ConstBox& operator=(const float * const * data)
            {
                data_ = data;
                return *this;
            }

            const float * const * data_;
        };


    public:

        S() : data(nullptr) {}
        explicit S(const float *const *const d) : data(d) {}

        ConstBox data;
    };

    S DoSomething() const
    {
        S   s(nullptr);

        auto f = []() -> S
        {
            S s;

            s.data = new float*[10];

            return s;
        };

        return f();
    }
};


typedef Outer::S S;

void FailTest()
{
    S   s;

    s.data = nullptr; // <-- fails
    float** v1 = s.data; // <-- fails
    const float** v1 = s.data; // <-- fails
    // These are ok
    const float* const* v2 = s.data;
}

【讨论】:

  • 但是S(data) 不会编译,因为data 不是const float * const *
  • 明确的 CTor:是的,我同意。事实上,我不需要这样做,因为生成的初始化列表已经这样做了。但我的观点是,它不像s.data = xxx 那样清晰易读,特别是如果结构中有很多成员是@rodrigo,GetS() 将无法编译。我在评论明确 CTor 的想法
  • @dzada 有很多不同的方法可以解决这个问题,使用构造函数只是其中一种。我认为,如果您在 lambda 中包含一个如何创建和变异 S 的示例,那么提供更接近您正在寻找的解决方案会更容易
  • @rodrigo float ** 可以转换为float const * const *。这对我来说编译得很好。
  • @dzada 根据您的其他 cmets,我添加了一个更接近您正在寻找的解决方案。
【解决方案3】:

@CaptainObvious 的答案是正确的。为 S 编写一个构造函数,接受它需要的任何参数,并使用成员初始化器而不是赋值语句来设置“数据”。

【讨论】:

  • 是的,我知道我可以做到这一点,我的问题是,当结构中有 20 个成员时,不清楚是否按顺序编写它们,编写 strust.member = 会更清楚。但如果我这样做,我就会失去 const 保护。此界面适用于我希望它简单且受保护的客户
  • 也正要取消删除它。必须进行一些更新,但正如您所说,在不从 data 中删除最终的 const CV 限定符的情况下,它几乎是正确的
  • 我不确定我们是否需要一个答案,只是说别人的答案是正确的,没有添加任何重要内容。
【解决方案4】:

使用您的简化示例,我只需这样做:

struct S{ float const * const * const data;};

auto create_buffer() -> float const * const * {
    float **buf;
    /* ... compute buffer contents */
    return buf;
}

S s {create_buffer()};

但是,您在评论中提到您有很多成员,并且基于顺序初始化成员还不够清楚。

struct S { const A a; const B b; const C c; };

S s {x,y,z}; // order based, not readable enough.

const成员必须作为对象构造的一部分进行初始化。您必须在初始化程序中以某种方式指定它们,或者必须在类中设置它们的值,以便在构造时设置它们。


解决方案 1

在构造期间传递它们的一种方法,但以一种可读的方式是使用第二个对象来帮助初始化:

struct S_initializer { A a; B b; C c; }

struct S {
  const A a; const B b; const C c;
  S(S_initializer &s) : a(s.a), b(s.b), c(s.c) {}
};

S make_S() {
  S_initializer s;
  s.a = x;
  s.b = y;
  s.c = z;
  return S{s};
}

上面涉及到一些重复,你可以通过将初始化助手对象设置为 S 的 const 成员来避免这种情况:

struct S {
  const S_initializer m;
  S(S_initializer &s) : m{s} {}
};

S make_S() {
  S_initializer s;
  s.a = x;
  s.b = y;
  s.c = z;
  return S{s};
}

权衡的是,现在要访问 S 的成员,您必须在其中有一个额外的 .m

A a = s.m.a; // versus just s.a;

解决方案 2

第二种方法依赖于编译器扩展;尽管不是标准的 C++,但 gcc 和 clang 在 C++ 中实现了 C99 指定的初始值设定项。 VC++ 没有实现这一点。

S s { .a = x, .b = y, .c = z };

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-09-08
    • 2019-09-02
    • 1970-01-01
    • 1970-01-01
    • 2013-01-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多