他们是否可以使用与成员函数相同的“点”表示法在对象上使用非成员非友元函数?
我可以从一个类中拉出(任何)成员,并让用户以与他们一直相同的方式使用它吗?
更长的解释:
Scott Meyers,Herb Sutter等人认为非成员非朋友函数是对象接口的一部分,可以改进封装。我同意他们的意见。
但是,在最近阅读了这篇文章之后: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 */
编辑:
感谢大家提供的非常有用的答案,但是,我认为我的问题的观点被忽略了。
我特别没有提到运营商的具体情况,以及它们如何保留“自然”符号。也不应该将所有内容都包装在命名空间中。这些内容写在我链接的文章中。
问题本身是:
在文章中,Herb建议一个insert()方法是成员,而其余的是非成员非朋友函数。
这意味着要使用一种形式的insert(),你必须使用点符号,而对于其他形式,则不需要。
这只是我,还是听起来很疯狂?
我有预感,也许您可以使用单一语法。 (我正在考虑Boost :: function如何为mem_fun提供*此参数)。
答案 0 :(得分:4)
是的,这意味着对象的部分界面由非成员函数组成。
对于T类的对象,你是否正确使用以下符号的事实:
void T::doSomething(int value) ; // method
void doSomething(T & t, int value) ; // non-member non-friend function
如果你想让doSomething函数/方法返回void,并且有一个名为“value”的int参数。
但有两件事值得一提。
第一个是类的接口的函数部分应该在同一个命名空间中。这是使用命名空间的另一个原因(如果需要另一个原因),如果只是“组合”一个对象和作为其接口一部分的函数。
好的一面是它促进了良好的封装。但不好的一点是,它使用类似功能的符号我个人不喜欢。
第二,运营商不受此限制。例如,类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 ;
}
从审美角度来看,这比功能性符号要好得多。
现在,能够从同一个界面编写一个函数并且仍能通过“。”调用它将是一个非常酷的语法糖。如michalmocny所提到的,就像在C#中一样。
假设无论出于什么原因,我想创建两个“类似整数”的类。 第一个是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个非会员非朋友功能。
这两个类可以用作:
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 ;
}
}
当我们比较运算符的使用(“+”和“+ =”)时,我们发现使运算符成为成员或非成员在其明显用途上没有区别。不过,有两点不同:
该成员可以访问其所有内部。非成员必须使用公共成员方法
从一些二元运算符,如+,*,有类型提升很有意思,因为在一种情况下(即lhs提升,如上所示),它不适用于成员方法。
现在,如果我们比较非运算符使用(“toString”),我们看到成员非运算符使用对于类Java开发人员而言比非成员函数更“自然”。尽管有这种不熟悉的情况,但对于C ++来说,重要的是接受它,尽管它的语法,非成员版本从OOP角度来看更好,因为它无法访问类内部。
作为奖励:如果你想要一个运算符(或非运算符函数)添加到没有运算符的对象(例如,&lt; windows.h&gt;的GUID结构),那么你可以,没有需要修改结构本身。对于运算符来说,语法是自然的,对于非运算符,语法很好......
免责声明:当然这些类很愚蠢:set / getValue几乎可以直接访问其内部。但是用Herb Sutter在Monoliths "Unstrung"中提出的字符串替换整数,你会看到一个更真实的案例。
答案 1 :(得分:1)
无法使用点表示法编写非成员非朋友,即因为“运算符”。不能超载。
您应该始终将非成员非友元类包装在匿名命名空间中(如果只有当前翻译单元需要这些功能)或者在用户的某个有意义的命名空间中。
答案 2 :(得分:1)
但是,在最近阅读了这篇文章之后:http://www.gotw.ca/gotw/084.htm我发现自己质疑语法含义。
语法含义可以在编写良好的C ++库中随处可见:C ++在所有地方都使用自由函数。这对于具有OOP背景的人来说是不寻常的,但它是C ++中的最佳实践。例如,请考虑STL标头<algorithm>
。
使用点符号因此成为规则的例外,而不是反过来。
请注意其他语言选择其他方法;这导致在C#和VB中引入了“扩展方法”,它允许模拟静态函数的方法调用语法(即正是你想到的)。然后,C#和VB是严格的面向对象语言,因此对方法调用使用单一符号可能更为重要。
除此之外,函数总是属于命名空间 - 虽然我自己偶尔会违反这个规则(但只在一个编译单元中,即我相当于main.cpp
,这里没有'发挥作用)。
答案 3 :(得分:1)
就个人而言,我喜欢免费功能的可扩展性。 size
函数就是一个很好的例子:
// joe writes this container class:
namespace mylib {
class container {
// ... loads of stuff ...
public:
std::size_t size() const {
// do something and return
}
};
std::size_t size(container const& c) {
return c.size();
}
}
// another programmer decides to write another container...
namespace bar {
class container {
// again, lots of stuff...
public:
std::size_t size() const {
// do something and return
}
};
std::size_t size(container const& c) {
return c.size();
}
}
// we want to get the size of arrays too
template<typename T, std::size_t n>
std::size_t size(T (&)[n]) {
return n;
}
现在考虑使用免费尺寸功能的代码:
int main() {
mylib::container c;
std::size_t c_size = size(c);
char data[] = "some string";
std::size_t data_size = size(data);
}
如您所见,您可以使用size(object)
而无需关心类型所在的命名空间(取决于参数类型,编译器会计算出命名空间本身),并且无需关心正在发生的事情在幕后。考虑使用begin
和end
作为自由函数。这正是boost::range
does的内容。
答案 4 :(得分:1)
你可以使用单一语法,但也许不是你喜欢的语法。不要在类范围内放置一个insert(),而是将其作为类的朋友。现在你可以写
了mystring s;
insert(s, "hello");
insert(s, other_s.begin(), other_s.end());
insert(s, 10, '.');
对于任何非虚拟的公共方法,它等同于将其定义为非成员友元函数。如果混合点/无点语法困扰你,那么一定要使这些方法成为友方函数。没有区别。
在future中,我们也可以编写这样的多态函数,所以这可能是C ++的方式,而不是人为地试图将自由函数强制为点语法。
答案 5 :(得分:1)
如果你想保留点符号,还要分离不需要成为类的朋友的函数(因此他们无法访问私有成员从而破坏封装),你可能会编写一个mixin类。要么在mixin中使“常规”插入纯虚拟,要么将其保持为非虚拟并使用CRTP:
template<typename DERIVED, typename T>
struct OtherInsertFunctions {
void insertUpsideDown(T t) {
DERIVED *self = static_cast<DERIVED*>(this);
self->insert(t.turnUpsideDown());
}
void insertBackToFront(T t) // etc.
void insert(T t, Orientation o) // this one is tricksy because it's an
// overload, so requires the 'using' declaration
};
template<typename T>
class MyCollection : public OtherInsertFunctions<MyCollection,T> {
public:
// using declaration, to prevent hiding of base class overloads
using OtherInsertFunctions<MyCollection,T>::insert;
void insert(T t);
// and the rest of the class goes here
};
无论如何都是这样的。但正如其他人所说,C ++程序员并不“假定”反对自由函数,因为你“应该”总是在寻找编写泛型算法的方法(比如std :: sort)而不是添加成员函数特别的课程。让所有方法始终如一的方法更具Java-y。
答案 6 :(得分:0)
是的,它们应该是全局的或命名空间范围的。 非成员非朋友函数在C#中看起来更漂亮,它们使用点表示法(它们被称为extension methods)。