我正在尝试创建一个简单的记录器类,我希望能够登录到通用ostream
(cout
/ cerr
)或文件。我想到的设计是允许构造函数采用ostream&
或文件名,在后一种情况下创建ofstream&
并将其分配给类'private ostream&
这样:
class Log {
private:
std::ostream& os;
public:
Log(std::ostream& os = std::cout): os(os) { }
Log(std::string filename) {
std::ofstream ofs(filename);
if (!ofs.is_open())
// do errorry things
os = ofs;
}
};
这样做会给我一个错误,ofstream
的赋值运算符是私有的。再看一遍,我想到,对本地对象的引用可能不起作用,并使os
指向ostream
并在堆上声明和删除它的工作与ofstream
案例ostream
虽然不是ostream
案例,os
已经存在且os
仅被os
引用(因为删除ofstream
的唯一地方将在构造函数中,我不知道如何确定os
是否指向在堆上创建的ofstream
。
那么我怎样才能使这项工作成功,即让{{1}}引用在构造函数中用文件名初始化{{1}}?
答案 0 :(得分:10)
首先,您无法在创建引用后重新绑定引用,只能初始化它们。您可能认为可以这样做:
Log(std::string filename) : os(std::ofstream(filename)) {
if (!os.is_open())
// do errorry things
}
但这并不好,因为你让os
引用一个临时变量。
当你需要一个必须可选的引用时,也就是说,它需要引用某些东西而不是其他时间,你真正需要的是指针 :
class Log {
private:
std::ostream* os;
bool dynamic;
public:
Log(std::ostream& os = std::cout): os(&os), dynamic(false) { }
Log(std::string filename) : dynamic(true) {
std::ofstream* ofs = new std::ofstream(filename);
if (!ofs->is_open())
// do errorry things and deallocate ofs if necessary
os = ofs;
}
~Log() { if (dynamic) delete os; }
};
以上示例只是为了向您展示正在发生的事情,但您可能希望使用智能指针进行管理。正如Ben Voigt指出的那样,有许多问题会导致你的程序出现无法预料和不受欢迎的行为;例如,当你试图制作上述类的副本时,它会击中粉丝。以下是使用智能指针的上述示例:
class Log {
private:
std::unique_ptr<std::ostream, std::function<void(std::ostream*)>> os;
public:
Log(std::ostream& os = std::cout): os(&os, [](ostream*){}) { }
Log(std::string filename) : os(new std::ofstream(filename), std::default_delete<std::ostream>()) {
if (!dynamic_cast<std::ofstream&>(*os).is_open())
// do errorry things and don't have to deallocate os
}
};
异常os(&os, [](ostream*){})
使指针指向给定的ostream&
,但当它超出范围时什么都不做;它赋予它一个什么也不做的删除函数。你可以在没有lambdas的情况下做到这一点,这个例子更容易。
答案 1 :(得分:8)
最简单的做法是将您的引用绑定到ofstream
,并确保ofstream
只要您的对象存在:
class Log
{
std::ofstream byname;
std::ostream& os;
public:
Log(std::ostream& stream = std::cout) : byname(), os(stream) { }
Log(std::string filename) : byname(filename), os(this->byname)
{
if (!os)
// handle errors
}
};
异常安全,无法泄漏,并且编译器生成的特殊成员函数是理智的。
答案 2 :(得分:0)
您必须在构造函数的初始化列表中初始化os
,就像您在Log(std::ostream& os = std::cout): os_(os) { }
中所做的那样,因为os
是一个引用,在初始化后无法分配
答案 3 :(得分:0)
在我的Log / Debug类中,我发现创建静态成员变量很有用:
class debug {
public:
...
// Overload operator() for printing values.
template<class Type1>
inline debug&
operator()(const std::string& name1,
const Type1& value1)
{
// Prettify the name/value someway in another inline function.
_stream << print_value(name1, value1) << std::endl;
return *this;
}
private:
...
static std::ostream& _stream;
};
然后在我的debug.cc文件中:
std::ostream& debug::_stream = std::cerr;