我读过Scott Meyers关于这个主题的article,并对他所谈论的内容感到很困惑。我这里有3个问题。
问题1
要详细解释,假设我正在使用vector<T>
,push_back
和运算符insert
等方法编写一个简单的[]
类。如果我遵循Meyers的算法,我最终会得到所有非会员朋友的功能。我将有一个带有很少私有成员和许多非成员朋友函数的向量类。这是他在说什么吗?
问题2
我仍然不了解非成员函数如何改进封装。考虑一下迈耶斯文章中给出的代码。
class Point {
public:
int getXValue() const;
int getYValue() const;
void setXValue(int newXValue);
void setYValue(int newYValue);
private:
... // whatever...
};
如果遵循他的算法,setXXXX
方法应该是非成员。我的问题是如何增加封装?他还说
我们现在已经看到了合理的方式 来衡量封装的数量 在一个类中是计算数量 如果这些功能可能被打破 班级的实施变化。
直到我们在类实现发生变化时保持方法签名完整,没有客户端代码会破坏并且封装得很好,对吧?这同样适用于非成员函数。那么非成员函数提供的优势是什么?
问题3
引用他的算法
else if (f needs type conversions
on its left-most argument)
{
make f a non-member function;
if (f needs access to non-public
members of C)
make f a friend of C;
}
他的意思 f需要在其最左边的参数上进行类型转换? 他还在文章中说了以下内容。
此外,我们现在看到了 共同声称“朋友的功能 违反封装“并不完全 真正。朋友不要违反 封装,他们只是减少它 - 以与成员完全相同的方式 功能
这个和上面的算法是矛盾的,对吧?
答案 0 :(得分:17)
问题1
在这种情况下,遵循Meyers的算法将为您提供成员函数:
operator<<
还是operator>>
?否。他的建议是只在他们真正需要的时候才能成为他们的朋友;支持非会员非朋友而不是朋友。
问题2
SetXXXX
函数需要访问类的内部(私有)表示,因此它们不能是非成员的非朋友;所以,迈耶斯认为,他们应该是成员而不是朋友。
通过隐藏类的实现细节来实现封装;您可以与私有实现分开定义公共接口。如果您随后发明了更好的实现,则可以在不更改公共接口的情况下进行更改,并且使用该类的任何代码都将继续工作。因此,Meyers的“可能被破坏的函数数量”计算成员和朋友函数(我们可以通过查看类定义轻松跟踪),但不是通过其公共接口使用该类的任何非成员非朋友函数。
问题3
从迈耶斯的建议中取消的重点是:
答案 1 :(得分:9)
f的含义需要类型转换,最左边的arg如下:
考虑遵循senario:
Class Integer
{
private:
int num;
public:
int getNum( return num;)
Integer(int n = 0) { num = n;}
Integer(const Integer &rhs)) { num = rhs.num ;}
Integer operator * (const Integer &rhs)
{
return Integer(num * rhs.num);
}
}
int main()
{
Integer i1(5);
Integer i3 = i1 * 3; // valid
Integer i3 = 3 * i1 ; // error
}
在上面的代码i3 = i1 * 3
相当于this->operator*(3)
,它有效,因为3被隐式转换为Integer。
在后面i3 = 3 * i1
中等同于3.operator*(i1)
的情况,根据规则,当u重载运算符使用成员函数时,调用对象必须属于同一个类。
但这不是那个。
要使Integer i3 = 3 * i1
工作,可以定义非成员函数,如下所示:
Integer operator * (const Integer &lhs , const Integer &rhs) // non-member function
{
return Integer(lhs.getNum() * rhs.getNum());
}
我想你会从这个例子中得到一个想法.....
答案 2 :(得分:1)
在他提出的非会员职能的四个案例中,你提出的vector
方法最接近的就是这个:
else if (f can be implemented via C's
public interface)
make f a non-member function;
但是,您无法通过公共接口实现push_back
,insert
或operator[]
等方法。那些是公共接口。有可能在push_back
方面实现insert
,但在很大程度上,您将使用哪种公共接口进行此类方法?
此外,为非成员函数提供友谊的案例非常特殊,我认为operator<<
和operator>>
以及类型转换都需要非常准确且未经过滤的数据。类。这些方法自然是非常具有侵略性的。
虽然我不是Dobbs博士或任何声称的“C ++大师”的粉丝,但我想在这种情况下你可能会双重猜测你自己的实现。 Scott Meyer的算法对我来说似乎很合理。
答案 3 :(得分:1)
仔细研究STL算法。 sort
,copy
,transform
等对迭代器进行操作,而不是成员函数。
你的算法也错了。使用Point的公共接口无法实现set和get函数。
答案 4 :(得分:1)
问题:2
如果你记得的话,Scott Meyers也提出了以下建议:- &GT;保持类接口完整且最小化。
见以下情景:
class Person {
private: string name;
unsigned int age;
long salary;
public:
void setName(string);// assme the implementation
void setAge(unsigned int); // assme the implementation
void setSalary(long sal); // assme the implementation
void setPersonData()
{
setName("Scott");
setAge(25);
selSalary(50000);
}
}
这里setPersonData()
是成员函数,但最终它的作用也可以通过使它成为非成员函数来实现,并且它将保持类的接口最小化并且不会不必要地充满成员函数的类。
void setPersonData(Person &p)
{
p.setName("Scott");
p.setAge(25);
p.selSalary(50000);
}
答案 5 :(得分:1)
我认为一般的观点是,如果可以的话,总是用其他东西来实现它是有益的。将功能实现为非朋友自由函数,可确保在更改类表示时此功能不会中断。
在现实生活中,我猜它可能有问题:您可能能够使用当前实现在公共接口方面实现某些功能,但如果类有更改,则可能不再可能了(你需要开始宣布朋友的事情)。 (例如,在算法优化方面,自由函数可能会受益于一些不应向公众公开的额外缓存数据。)
所以我从中得出的指导原则:使用常识,但不要害怕自由功能。它们不会使您的C ++代码不那么面向对象。
另一件事可能是一个完全由getter和setter组成的接口。这几乎不包含任何东西。
特别是在Point的情况下,你可能会试图将数据存储为int coords[2]
,而在这方面,getter和setter可能有意义(但也可能总是考虑使用的方便性) vs易于实施)。
但是如果你转向更复杂的类,他们应该做某些事情(一些核心功能),而不仅仅是提供对他们数据的访问。
当涉及到vector时,它的一些方法可能是自由函数:assign(就clear + insert而言),at,back,front(就大小+ operator[]
而言),empty(in大小或开始/结束的术语),pop_back
(擦除+大小),push_back
(插入+大小),结束(开始+大小),rbegin和rend(开始和结束)。
但如果采取严格措施,这可能会导致相当混乱的界面,例如
for (vector<T>::iterator it = v.begin(); it != end(v); ++it)
此外,这里必须考虑其他容器的功能。如果std :: list不能作为自由函数实现end,那么std :: vector也不应该(模板需要一个统一模式来迭代容器)。
再次,使用常识。
答案 6 :(得分:0)
他特别说“非会员非朋友功能”(强调我的)。如果你需要将非成员函数作为一个恶魔,他的算法说它应该是一个成员函数,除非它是运算符&gt;&gt;或运算符&lt;&lt;或者需要对其最左边的参数进行类型转换。
答案 7 :(得分:0)
直到我们保留方法签名 类实现时完好无损 变化,没有客户代码会破裂 它封装得很好,对吗? 这同样适用于非会员 功能也是如此。那是什么呢? 优势非会员功能 提供?
Meyers说,具有许多方法的类比具有更少方法的类封装更少,因为所有这些内部方法的实现都会发生变化。如果任何方法可能是非成员,那将减少可能受到类内部更改影响的方法的数量。
他的意思是f需要类型 转换最左边的论点?
我认为他指的是运算符,如果它们是成员函数,则会有一个隐含最左边的参数。