他们是否可以使用与成员函数相同的“点”表示法在对象上使用非成员非友元函数?
我能否将(任何)成员从类中拉出来,让用户像往常一样使用它?
更长的解释:
Scott Meyers , Herb Sutter 等人认为非成员非友元函数是对象接口(interface)的一部分,并且可以改进封装。我同意他们的看法。
然而,在最近阅读这篇文章之后:http://www.gotw.ca/gotw/084.htm我发现自己在质疑语法含义。
在那篇文章中,Herb 提议使用一个insert
、erase
和replace
成员,以及几个非成员非友元函数同名。
这是否意味着,如我所想,Herb 认为某些函数应该与点符号一起使用,而其他函数应作为全局函数使用?
std::string s("foobar");
s.insert( ... ); /* One like this */
insert( s , ...); /* Others like this */
编辑:
感谢大家提供非常有用的答案,但是,我认为我的问题的要点被忽略了。
我特别没有提到运算符的具体情况,以及它们如何保留“自然”符号。也不应该将所有内容包装在 namespace 中。这些东西写在我链接的文章中。
问题本身是:
在文章中,Herb 建议一个 insert() 方法是成员函数,而其余的是非成员非友元函数。
这意味着要使用一种形式的 insert(),您必须使用点表示法,而对于其他形式,则不需要。
这只是我,还是听起来很疯狂?
我有一种预感,也许您可以使用一种语法。 (我在想 Boost::function 如何为 mem_fun 使用 *this 参数)。
最佳答案
是的,就是说一个对象的部分接口(interface)是由非成员函数组成的。
你是对的,它涉及对类 T 的对象使用以下符号:
void T::doSomething(int value) ; // method
void doSomething(T & t, int value) ; // non-member non-friend function
如果您希望 doSomething 函数/方法返回 void,并有一个名为“value”的 int 参数。
但有两件事值得一提。
首先是类接口(interface)的函数部分应该在同一个命名空间中。这是使用命名空间的另一个原因(如果需要另一个原因的话),如果只是为了“组合”一个对象和作为其接口(interface)一部分的函数。
好的部分是它促进了良好的封装。但不好的是它使用了类似函数的符号,我个人非常不喜欢。
二是运营商不受此限制。例如,类 T 的 += 运算符可以用两种方式编写:
T & operator += (T & lhs, const T & rhs) ;
{
// do something like lhs.value += rhs.value
return lhs ;
}
T & T::operator += (const T & rhs) ;
{
// do something like this->value += rhs.value
return *this ;
}
但这两种表示法都用作:
void doSomething(T & a, T & b)
{
a += b ;
}
从美学的角度来看,这比类函数符号要好得多。
现在,如果能够从同一个接口(interface)编写一个函数,并且仍然能够通过“.”调用它,那将是一个非常酷的语法糖。符号,就像在 C# 中一样,如 michalmocny 所提到的。
编辑:一些例子
假设我出于某种原因想要创建两个“类整数”类。 第一个是 IntegerMethod:
class IntegerMethod
{
public :
IntegerMethod(const int p_iValue) : m_iValue(p_iValue) {}
int getValue() const { return this->m_iValue ; }
void setValue(const int p_iValue) { this->m_iValue = p_iValue ; }
IntegerMethod & operator += (const IntegerMethod & rhs)
{
this->m_iValue += rhs.getValue() ;
return *this ;
}
IntegerMethod operator + (const IntegerMethod & rhs) const
{
return IntegerMethod (this->m_iValue + rhs.getValue()) ;
}
std::string toString() const
{
std::stringstream oStr ;
oStr << this->m_iValue ;
return oStr.str() ;
}
private :
int m_iValue ;
} ;
这个类有 6 个方法可以访问它的内部。
第二个是 IntegerFunction:
class IntegerFunction
{
public :
IntegerFunction(const int p_iValue) : m_iValue(p_iValue) {}
int getValue() const { return this->m_iValue ; }
void setValue(const int p_iValue) { this->m_iValue = p_iValue ; }
private :
int m_iValue ;
} ;
IntegerFunction & operator += (IntegerFunction & lhs, const IntegerFunction & rhs)
{
lhs.setValue(lhs.getValue() + rhs.getValue()) ;
return lhs ;
}
IntegerFunction operator + (const IntegerFunction & lhs, const IntegerFunction & rhs)
{
return IntegerFunction(lhs.getValue() + rhs.getValue()) ;
}
std::string toString(const IntegerFunction & p_oInteger)
{
std::stringstream oStr ;
oStr << p_oInteger.getValue() ;
return oStr.str() ;
}
它只有 3 个方法,因此减少了可以访问其内部的代码量。它有3个非成员(member)非好友功能。
这两个类可以用作:
void doSomething()
{
{
IntegerMethod iMethod(25) ;
iMethod += 35 ;
std::cout << "iMethod : " << iMethod.toString() << std::endl ;
IntegerMethod result(0), lhs(10), rhs(20) ;
result = lhs + 20 ;
// result = 10 + rhs ; // WON'T COMPILE
result = 10 + 20 ;
result = lhs + rhs ;
}
{
IntegerFunction iFunction(125) ;
iFunction += 135 ;
std::cout << "iFunction : " << toString(iFunction) << std::endl ;
IntegerFunction result(0), lhs(10), rhs(20) ;
result = lhs + 20 ;
result = 10 + rhs ;
result = 10 + 20 ;
result = lhs + rhs ;
}
}
当我们比较运算符的使用(“+”和“+=”)时,我们发现将运算符设为成员或非成员在其表面用法上没有区别。尽管如此,还是有两个区别:
成员可以访问其所有内部信息。非成员必须使用公共(public)成员方法
对于某些二元运算符,如 +、*,进行类型提升很有趣,因为在一种情况下(即 lhs 提升,如上所示),它不适用于成员方法。
现在,如果我们比较非运算符使用(“toString”),我们会发现对于类 Java 开发人员来说,成员非运算符使用比非成员函数更“自然”。尽管不熟悉,但对于 C++ 来说,重要的是要接受这一点,尽管它有语法,但从 OOP 的角度来看,非成员版本更好,因为它无法访问类内部。
作为奖励:如果你想添加一个运算符(resp. 一个非运算符函数)到一个没有运算符的对象(例如,
免责声明:当然这些类是愚蠢的:set/getValue 几乎可以直接访问其内部结构。但是用字符串替换整数,正如 Herb Sutter 在 Monoliths "Unstrung" 中提出的那样,你会看到一个更真实的案例。
关于c++ - 非成员非友元函数语法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/332460/