重载流插入而不违反信息隐藏?
Posted
技术标签:
【中文标题】重载流插入而不违反信息隐藏?【英文标题】:Overloading stream insertion without violating information hiding? 【发布时间】:2010-06-07 03:46:32 【问题描述】:我在一个项目中使用yaml-cpp。我想为某些类重载 <<
和 >>
运算符,但我在努力解决如何“正确”执行此操作时遇到问题。以Note
类为例。挺无聊的:
class Note
public:
// constructors
Note( void );
~Note( void );
// public accessor methods
void number( const unsigned long& number ) _number = number;
unsigned long number( void ) const return _number;
void author( const unsigned long& author ) _author = author;
unsigned long author( void ) const return _author;
void subject( const std::string& subject ) _subject = subject;
std::string subject( void ) const return _subject;
void body( const std::string& body ) _body = body;
std::string body( void ) const return _body;
private:
unsigned long _number;
unsigned long _author;
std::string _subject;
std::string _body;
;
<<
运算符很简单。在.h
:
YAML::Emitter& operator << ( YAML::Emitter& out, const Note& v );
而在.cpp
:
YAML::Emitter& operator << ( YAML::Emitter& out, const Note& v )
out << v.number() << v.author() << v.subject() << v.body();
return out;
没有汗水。然后我去声明>>
操作符。在.h
:
void operator >> ( const YAML::Node& node, Note& note );
但是在.cpp
我得到:
void operator >> ( const YAML::Node& node, Note& note )
node[0] >> ?
node[1] >> ?
node[2] >> ?
node[3] >> ?
return;
如果我写 node[0] >> v._number;
之类的东西,那么我需要更改 CV 限定符以使所有 Note
字段 public
(这与我所教的一切(教授、书籍和经验)都失败了) ) 关于数据隐藏。
我觉得到处都在做node[0] >> temp0; v.number( temp0 );
不仅乏味、容易出错和丑陋,而且相当浪费(额外的副本会怎样)。
然后我聪明了:我试图将这两个运算符移动到 Note
类本身,并将它们声明为 friend
s,但编译器 (GCC 4.4) 不喜欢这样:
src/note.h:44: error: ‘YAML::Emitter& Note::operator src/note.h:45: error: ‘void Note::operator>>(const YAML::Node&, Note&)’ 必须只有一个参数
问题:如何“正确”重载类的 >>
运算符
-
不违反信息隐藏原则?
没有过度复制?
【问题讨论】:
将operator<<
作为成员函数移动时的错误告诉您,当您将运算符重写为成员函数时,左侧操作数必须是该类类型,而右侧是运算符的 only 参数。您不能覆盖将YAML::Emitter
作为第一个参数作为YAML::Emitter
类之外的类成员的运算符
考虑根据const
参考返回字符串。
【参考方案1】:
在不违反封装的情况下执行此操作的典型方法是使 operator>> 成为友元函数。您对友元运算符的声明一定存在语法问题(从错误消息中不清楚究竟是什么)。我不使用 YAML,但从您的问题来看,以下是它的要点:
class Note
...
friend void operator >> ( const YAML::Node& node, Note& note );
....
;
void operator >> ( const YAML::Node& node, Note& note )
node[0] >> note._number;
node[1] >> note._author;
node[2] >> note._subject;
node[3] >> note._body;
朋友函数对私有成员的访问权限与成员函数相同。
或者,您可以为所有成员数据声明 setter,但友元函数方法更简洁。
【讨论】:
我同意所有的 setter 都是不完美的,但他已经掌握了所有这些,他们只是在这个用例中采用了一个不太有用的参数类型——所以,添加重载与实际- 有用的参数类型几乎不会增加任何问题。 @Alex Martelli 也许,但是声明一个友元函数(以及它的清洁器,恕我直言)也不是任何问题。 我不确定是什么问题,但我今天又试了一次,它成功了;我认为你是对的,一定是语法问题。【参考方案2】:我喜欢使用辅助方法。由于该方法是类的一部分,因此它可以完全访问所有私有字段:
class Note
public:
void read(const YAML::Node& node)
node >> ...;
;
然后让operator>>
转接电话:
const YAML::Node &operator >> ( const YAML::Node& node, Note& note )
note.read(node);
return node;
【讨论】:
我不会这样做。该类已经有一个写入接口,并且添加此方法会生成从 Note 到 YAML 的依赖关系,这并不是真正需要的(您不能再在没有 YAML 的上下文中使用 Note)。【参考方案3】:你在Note
中定义了更多的setter方法,比如
void number(YAML::Immitter& e) e>>_number;
等等,然后你定义语法糖>>
为
void operator >> ( YAML::Immitter& e, Note& note )
note.number(e);
note.author(e);
note.subject(e);
note.body(e);
我不熟悉您正在使用的 YAML 命名空间(我知道 yaml
,但我从未在 C++ 中处理过它),但这大致就是您使用普通流的方式(除了 @987654326 @return types;-),我相信它可以很容易地适应您的确切需求。
【讨论】:
【参考方案4】:你的类已经有了 setter 方法。只需使用临时对象读取值并使用 setter 方法配置对象:
void operator >> ( const YAML::Emitter& node, Note& note )
unsigned long number;
unsigned long author;
// ...
node[0] >> number;
node[1] >> author;
// ... everything properly read, edit the node:
node.number(number);
node.author(author);
// ...
return;
其他一些 cmets:几乎没有封装所有属性的 setter/getter 类。您为用户提供了与您的字段实际上是公开的相同的访问级别(唯一的优点是您可以在以后添加检查,但封装仍然很弱)。
在建议添加采用 YAML 节点的成员方法的解决方案中,这将为您的类的所有用户添加额外的依赖项。虽然您可以使用前向声明来避免强制它们包含 YAML 标头,但您将无法使用您的 Note
拉取库以用于其他不容易使用 YAML 的项目。
资源的潜在浪费使用可能会非常有限。再说一遍,和往常一样,先衡量,如果遇到问题,再尝试解决。
【讨论】:
我完全同意“几乎没有封装”的评论。就目前而言,无论如何,所有数据成员实际上都是公开的。没有太多“信息隐藏原则”可以违反。 类中的所有数据成员都可以在以后有效地删除/重命名/即时计算,而无需对用户界面进行任何更改。使用 getter/setter 实现的信息是完全隐藏的。这只是使用公共类的唯一方式。 @Alsk,从理论的角度来看,我同意,但根据我的经验,在所有项目中,我都看到这种模式与普通公共属性相比唯一真正的优势是可以向班上。无论如何,这主要是对封装的评论,重点是使用允许您完全修改状态的公共设置器,您不需要任何其他东西,您可以在这些设置器之上构建。在现有公共接口之上构建功能可以减少耦合。【参考方案5】:嗯,这是一个您可以考虑的想法。您所说的非朋友、非成员
inputter& operator >> (inputter& in, my_type & obj)
input_helper<my_type> helper(obj);
in >> helper.setter(&my_type::number);
in >> helper.setter(&my_type::subject);
// etc
input_helper
的职责只是提供模板函数setter()
,它返回一个对象,该对象简单地读取值并用它调用setter,创建必要的临时变量。像这样的代码需要对模板有一定的了解,但不会特别困难。现在不能完全正确地思考——可能是感冒了——或者我可能只能把它打出来。也许有点像这样:
template < typename T >
struct input_helper
input_helper(T & t) : obj(t)
template < typename V >
struct streamer
streamer(T & t, void (T::*f)(V const&)) : obj(t), fun(f)
template < typename Stream >
Stream& read_from(Stream & str) const // yeah, that's right...const; you'll be using a temporary.
V v;
str >> v;
obj.(*fun)(v);
return str;
private: // you know the drill...
template < typename V >
streamer setter(void (T::*fun)(V const&))
return streamer(obj, fun);
private:
T & obj;
;
// etc... operator >> (blah blah) return setter.read_from(stream);
其中肯定有各种各样的错误,但它应该给你一个想法。还需要更多的工作来概括。
【讨论】:
我首先将其阅读为helper.setter( &my_type::number_ )
,这将给出一个错误,因为number_
是私有的。然而,使用 &my_type::number
也被破坏了,因为有两个 number
函数。你需要一些像( unsigned long(mytype::*)() )( &my_type::number)
这样的可憎来消除它们的歧义......比使用临时值恕我直言更糟糕。
我相信函数参数类型可以消除它们的歧义。我没有测试任何它。即使没有,也有很多方法可以解决您所说的问题。
我测试了代码的这个特定方面,它确实编译得很好。地址所指的数字版本由分配给它的类型(函数参数)来消除歧义。以上是关于重载流插入而不违反信息隐藏?的主要内容,如果未能解决你的问题,请参考以下文章