"不可改变" struct with non-trivial constructor

时间:2016-06-09 13:49:47

标签: c++ c++11

我一直在寻找一种方法来使用非平凡的构造函数声明某种不可变类型。我目前的目标是从文件中读取数据以构造对象,以便随后不能对其进行修改。它类似于POD类型,除了我需要文件中的数据,因此构造函数必须读取它。

通过我的研究和实验,我想到了三种方法。基本上,我的问题是:有没有更好的方法来做我想要的?

在以下示例代码中,我将使用std::cin替代文件。首先,这是明显的类与吸气者的方式:

class A {
public:
    A() { std::cin >> m_i; }
    int i() { return m_i; }

private:
    int m_i;
};

事实上,我在使用这个解决方案时遇到了麻烦,仅仅是因为吸气剂。毕竟,它是一种POD类型,我希望它与公共数据成员一样对待。而且,我只是不喜欢吸气剂。所以我尝试了一些const - 并通过调整构造函数:

struct B {
    B() : B(B::fromFile()) {
    }

    B(int i) : i(i) {
    }

    const int i;

private:
    static B fromFile() {
        int i;
        std::cin >> i;
        return B(i);
    }
};

这里有几个问题。我需要委托一个静态方法,因为我不能直接在构造函数的初始化列表中获取成员的值。这个方法需要创建每个成员的副本(这里只是i)并单独初始化它们,以便它可以在使用复制构造函数最终构造初始对象之前将它们传递给另一个构造函数。此外,由于新的构造函数和静态方法,它需要更多的代码行。

所以,这种方法似乎注定要失败。然后我意识到,我真正想要的是该类/结构的每个实例都是const。但是,据我所知,没有办法强迫用户每次都使用const关键字。所以,我想到了using别名声明。有点像标准库为const_reference做的事情(几乎每个容器)。只有在这种情况下,它会反过来:类型将被称为NonConstType,或者让我们说MutableType,并且别名将被声明为:

using Type = const MutableType;

由于我不想污染命名空间,因此请使用Mutable命名空间。这是代码的样子:

namespace Mutable {
    struct C {
        C() { std::cin >> i; }

        int i;
    };
}

using C = const Mutable::C;

这样,我可以提供一个"不可变的"像C结构一样处理的类(没有getter),但仍然可以使用来自不同文件的数据构建。此外,可变版本仍然可用,我认为这毕竟是件好事。

那么,还有另外一种方法吗?在这三个代码中的任何一个代码中都没有我想到的好处或缺点吗?

A full testing code can be found here.

7 个答案:

答案 0 :(得分:2)

使用模板阅读助手怎么样?你不需要它是静态的或类成员,只需要一个免费模板就可以从流中提取正确的值。

#include <iostream>

template <typename T>
T stream_read(std::istream& is)
{
    T val;
    is >> val;

    return val;
}

struct B
{
    B() : i_(stream_read<int>(std::cin)) { }

    const int i_;
};

int main()
{
    B b;

    std::cout << "value=" << b.i_ << std::endl;
}

答案 1 :(得分:2)

您正在使用C ++ 11,那么为什么不使用聚合初始化?

#include <iostream>

struct Foo {
    const int val; // Intentionally uninitialized.
};

struct Foo create_foo_from_stream (std::istream& stream) {
    int val;
    stream >> val;
    return Foo{val};
}

int main () {
    Foo foo (create_foo_from_stream(std::cin));
    std::cout << foo.val << '\n';
}

初始化struct Foo的唯一方法是通过聚合初始化或复制构造。隐式删除默认构造函数。

请注意,在C ++ 14中,您可以使用默认成员初始值设定程序并仍然使用聚合初始化:

#include <iostream>

struct Foo {
    const int val = 0;  // Prevents aggregate in C++11, but not in C++14.
};

struct Foo create_foo_from_stream (std::istream& stream) {
    int val;
    stream >> val;
    return Foo{val};
}

int main () {
    Foo foo (create_foo_from_stream(std::cin));
    Foo bar;   // Valid in C++14.
    std::cout << foo.val << bar.val << '\n';
}

答案 2 :(得分:0)

你正在使用C ++ 11所以......为什么不使用默认初始化?

B派生解决方案

#include <iostream>

struct B2 {
    const int i { fromFile("filename1") };
    const int j { fromFile("filename2") };
    const int k { fromFile("filename3") };

private:
    static int fromFile (const std::string &) {
        int i;
        std::cin >> i;
        return i;
    }
};

int main ()
 {
   B2 a;

   // a.i = 5; // error: i is const
   // a.j = 7; // error: j is const
   // a.k = 9; // error: k is const

   return 0;
 }

答案 3 :(得分:0)

轮到我了。 普通的C ++ 03,没有聚合。

struct B {
    B(): i_(read()), j_(read()) {}
    int i() { return i_; }
    int j() { return j_; }
private:
    int read() { int retVal; cin >> retVal; return retVal; }
    const int i_, j_;
};

如果只有一个属性,事情可能更简单:

struct B {
    B();
    operator int() { return i; }
private:
    const int i;
};

答案 4 :(得分:0)

您要找的是成员初始值设定列表返回类型优化。成员初始化列表是一个初始化非静态const成员的地方:

struct A{
   A():i{some_function()}{}
   const int i;
}

成员初始值设定项列表在构造函数声明之后和构造函数定义之前由“:”引入::i{some_function}

somme_function可以是任何可调用的。为了保持最佳状态,请启用返回类型优化。成员i没有返回值的副本。例如:

int some_function(){
  int a;
  cin >> a;
  return a;
}

变量a是在调用者的上下文中构造的:变量a“引用”成员i。没有副本。上面的代码是最佳的,它将您的文件直接写入成员i

宣言:

auto a = A();
根据生成的代码,

等同于:

cin >> a.i;

要启用此优化,请在函数体内声明返回值并按值返回。

如果需要多个变量,解决方案是匿名联合(标准)和匿名结构(非标准):

struct A{
  struct data_structure {
    int i;
    int j;
    };
  struct raw_data_t{
    unsigned char data[sizeof(data_structure)];
  };
  union{
    const raw_data_t raw_data;
    struct{
    const int i;
    const int j;
    };
  };
  A():raw_data{some_function()}{}
  raw_data_t some_function(){
    raw_data_t raw_data;
    auto data = new(&raw_data) data_structure();
    cin >> data->i;
    cin >> data->j;
    return raw_data;
  }
};

这不太性感!而不是标准,所以不便携。

所以让我们等待C ++ 17运算符。()重载!!

答案 5 :(得分:0)

如果我做得不好,你需要通过一次调用单个函数来对你的所有数据进行初始化。

我想你可以将你的类包装在另一个数据类中;例如,假设您需要intlongstd::string,您可以执行类似

的操作
#include <iostream>

struct B3_Data {
   const int i;
   const long j;
   const std::string k;
};

struct B3 {

   using  dataType = B3_Data;

   const dataType  data = fromFile();

private:
   static dataType fromFile () {
      int i;
      long j;
      std::string k;
      std::cin >> i >> j >> k;
      return dataType {i, j, k};
    }
};

int main ()
 {
   B3 a;

   //a.data.i = 5; // error: data.i and i are const

   return 0;
 }

如果您的数据不多,可以将所有数据打包到std::tuple;

之类的东西
#include <tuple>
#include <iostream>

struct B4 {

   using  dataType = std::tuple<int, long, std::string>;

   const dataType  data { fromFile() };

private:
   static dataType fromFile () {
      int i;
      long j;
      std::string k;
      std::cin >> i >> j >> k;
      return std::make_tuple(i, j, k);
    }
};


int main ()
 {
   B4 a;

   std::cout << std::get<0>(a.data) << std::endl;  // ok: reading

   // std::get<0>(a.data) = 5;                        // error: data is const

   return 0;
 }

答案 6 :(得分:0)

您提到强制const - 将您的类型作为解决方案,但不确定如何强制客户端将对象构建为const。您可以使用工厂来实现这一目标:

struct MyType {
 public:
  static const MyType FromStream(std::istream &is) {
    return MyType(is);  // Most likely optimized as a move through copy elision 
  }
  static const MyType *MakeNewFromStream(std::istream &is) {
    return new MyType(is);
  }

  int a, b;
 private:
  MyType(std::istream &is) {
    is >> a >> b;
  }
};

并在使用中:

const MyType mt = MyType::FromStream(std::cin);
const MyType mt_ptr = MyType::MakeNewFromStream(std::cin);