【问题标题】:How do I use a stream extraction operator if a default constructor isn't provided?如果未提供默认构造函数,如何使用流提取运算符?
【发布时间】:2021-07-19 10:52:48
【问题描述】:

我有一个关于 C++、构造函数(默认和其他)、流提取运算符和引用成员变量的软件工程问题。我知道这是很多主题,但在这个问题上它们似乎都相互依赖。

对于我试图解决的问题,我想创建一个 Vec 类来保存问题的数据,以及一个 ProblemSolver 类,它最终将拥有解决问题的方法。 ProblemSolver 类没有数据就没有意义,所以我用Vec 数据的副本编写了它(参考成员的考虑后来发生了......)。所以这是我的第一个版本的样子:

#include <iostream>
#include <vector>

class Vec {
private:
    std::vector<int> v;
public:
    Vec(const int s) : v(s, 0) { }
};

class ProblemSolver {
private:
    Vec data;
public:
    ProblemSolver(Vec d) : data(d) { }
};

int main() {
    int m;
    std::cin >> m;
    Vec v(m);
    ProblemSolver ps(v);
}

到目前为止,这似乎还不错。然后我认为我应该为每个类提供流提取运算符,以便对象可以负责读取自己的数据,而不是在 main 中读取它。所以我重写了这样的类:

#include <iostream>
#include <vector>

class Vec {
private:
    std::vector<int> v;
public:
    Vec(const int s) : v(s, 0) { }
    friend std::istream& operator >> ( std::istream& input, Vec &V ) {
        int m;
        input >> m;
        V.v = std::vector<int>(m, 0);
        return input;
    }
};

class ProblemSolver {
private:
    Vec data;
public:
    ProblemSolver(Vec d) : data(d) { }
    friend std::istream& operator >> ( std::istream& input, ProblemSolver &P ) {
        input >> P.data;
        return input;
    }
};

int main() {
    ProblemSolver p;
    std::cin >> p;
}

这是我遇到一些困惑的地方,因为main() 的第一行试图调用ProblemSolver 的默认构造函数。我知道这是一个问题,因为没有提供,但是就上述问题的良好实践而言,我应该怎么做呢?我是否应该始终提供默认构造函数,即使没有实际数据集的类没有意义?我应该让对象处理自己的数据读取,而我不应该编写流提取运算符,我错了吗?

因为this article,我考虑使用引用成员变量,因为“问题解决者类的数据仍然存在于问题解决者实例存在之外”。于是我又改写成这样:

#include <iostream>
#include <vector>

class Vec {
private:
    std::vector<int> v;
public:
    Vec(const int s) : v(s, 0) { }
    friend std::istream& operator >> ( std::istream& input, Vec &V ) {
        int m;
        std::cin >> m;
        V.v = std::vector<int>(m, 0);
        return input;
    }
};

class ProblemSolver {
private:
    const Vec& v;
public:
    ProblemSolver(const Vec& v_) : v(v_) { }
};

int main() {
    int m;
    std::cin >> m;
    Vec v(m);
    ProblemSolver p(v);
}

但是有了这个,我仍然无法为ProblemSolver 编写流提取操作符,而为Vec 编写的操作符实际上没用,因为我必须先实例化一个Vec 对象才能使用流提取运算符。因此,在上面的示例中,我仍然需要读取main() 中的int m 数据,并且由于与第二个代码段相同的原因,我不能执行类似Vec v; std::cin &gt;&gt; v; ProblemSolver p(v); 的操作。我认为应该可以像 std::cin &gt;&gt; ProblemSolver p; 那样一次性实例化和读取变量,但显然情况并非如此。

所以我的主要问题是如何正确编写这两个类?我应该包含默认构造函数吗?我的理解是,如果类在没有任何数据的情况下没有意义,我不应该提供默认构造函数,但这是错误的吗?如果我不编写默认构造函数,我是否应该使用提供给标准构造函数的无用数据来实例化一个类,以便我可以在其上使用流提取运算符?在流提取之前错误地实例化对象似乎是一种不好的做法。

需要明确的是,我不只是在寻找可行的解决方案。我想针对此类情况开发实际的良好做法和技术,因此请以正当理由支持您的回答。

【问题讨论】:

  • 流提取运算符的一个不幸的设计限制是它们仅适用于可以分配给的对象,因此如果您确定为ProblemSolver 提供它们,则无需添加默认 ctor (或其道德等价物——以某种“默认”状态构造 ProblemSolver 实例的某种方式,您将立即覆盖该状态)。如果您只想让ProblemSolver 从流中初始化,只需添加一个直接采用std::istream 的ctor。

标签: c++ default-constructor


【解决方案1】:

在这种情况下,必须先构造对象。

一些选项是:

  • 有一个默认的 costructor,它使用默认值(可能是 0)进行构造。

  • 有一个构造函数,它引用istream,然后将使用它来加载值。

类似:

class Vec {
private:
    std::vector<int> v;
public:
    Vec(const int s) : v(s, 0) { }
    Vec(std::istream& input) 
    {
        int m;
        input >> m;
        v = std::vector<int>(m, 0);
    }
    friend std::istream& operator >> ( std::istream& input, Vec &V ) {
        int m;
        input >> m;
        V.v = std::vector<int>(m, 0);
        return input;
    }
};


class ProblemSolver {
private:
    Vec v;
public:
    ProblemSolver(Vec v_) : v(v_) { }
    ProblemSolver() : v(0) { }
    ProblemSolver(std::istream& in) : v(in) { } 
};

如果您坚持认为没有值的类将没有意义,那么您最好编写一个生成器函数并放弃流提取运算符。像这样:


class Vec {
private:
    std::vector<int> v;
public:
    Vec(const int s) : v(s, 0) { }
};

class ProblemSolver {
private:
    Vec data;
public:
    ProblemSolver(Vec d) : data(d) { }
    static ProblemSolver generate()
    {
        int m;
        std::cin >> m;
        return ProblemSolver{Vec{m}};
    }
};

int main() {
    ProblemSolver p = ProblemSolver::generate();
}

【讨论】:

  • 目前,我正在使用上述两者的组合:一个以 istream& 作为输入的静态生成器。我担心这有两个原因:(1)流提取重载返回原始流,我不能使用生成器,因为我已经返回对象[不确定这是否会成为问题],以及( 2)我担心生成器中对象的返回是通过复制返回(较慢)并且只能通过NRVO避免。想法?
  • @redmoncoreyl 关于问题 (2) 参见 en.cppreference.com/w/cpp/language/copy_elision 这里声明从 c++17 开始,这种特殊类型的复制省略是强制性的。
  • @redmoncoreyl 关于关注点(1)在这种情况下不需要返回std::istream&amp;&lt;&lt; 运算符返回一个引用以启用链接,这里不需要。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-28
  • 2014-06-13
  • 1970-01-01
  • 1970-01-01
  • 2013-03-16
  • 1970-01-01
相关资源
最近更新 更多