繁体   English   中英

非成员非朋友函数语法

[英]non-member non-friend function syntax

他们是否可以使用与成员函数相同的“点”表示法在对象上使用非成员非友元函数?

我可以从一个类中拉出(任何)成员,并让用户以他们一直使用的方式使用它吗?

更长的解释:

Scott Meyers ,Herb Sutter等人认为,非成员非朋友函数是对象接口的一部分,可以改进封装。 我同意他们。

但是,在最近阅读本文之后: http//www.gotw.ca/gotw/084.htm我发现自己质疑语法含义。

在那篇文章中,Herb建议使用一个inserterasereplace成员,以及几个同名的非成员非朋友函数。

这是否意味着,正如我认为的那样,Herb认为某些函数应该与点符号一起使用,其他函数应该作为全局函数使用?

std::string s("foobar");

s.insert( ... ); /* One like this */
insert( s , ...); /* Others like this */

编辑:

感谢大家提供的非常有用的答案,但是,我认为我的问题的观点被忽略了。

我特别没有提到运营商的具体情况,以及他们如何保留“自然”符号。 也不应该将所有内容都包装在命名空间中。 这些东西写在我链接的文章中。

问题本身是:

在文章中,Herb建议一个insert()方法是成员,而其余的是非成员非朋友函数。

这意味着要使用一种形式的insert(),你必须使用点符号,而对于其他形式,则不需要。

这只是我,还是听起来很疯狂?

我有一种预感,也许你可以使用单一语法。 (我在想如何Boost :: function可以为mem_fun取一个* this参数)。

是的,这意味着对象的部分接口由非成员函数组成。

对于类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 ;
   }
}

当我们比较运算符的使用(“+”和“+ =”)时,我们发现将运算符作为成员或非成员使用它的表面用法没有区别。 不过,有两点不同:

  1. 该成员可以访问其所有内部。 非成员必须使用公共成员方法

  2. 从一些二元运算符,如+,*,有类型提升很有意思,因为在一种情况下(即lhs提升,如上所示),它不适用于成员方法。

现在,如果我们比较非运算符使用(“toString”),我们看到成员非运算符使用对于类Java开发人员而言比非成员函数更“自然”。 尽管不熟悉,但对于C ++来说,重要的是要接受它,尽管它的语法,非成员版本从OOP角度来看更好,因为它无法访问类内部。

作为奖励:如果要将操作符(或非操作符函数)添加到没有操作符的对象(例如,<windows.h>的GUID结构),则可以,而无需修改结构本身。 对于运算符,语法将是自然的,对于非运算符,则...

没有办法用点表示法写一个非成员非朋友,即因为“运算符”。 不能超载。

您应始终将非成员非友元类包装在匿名命名空间中(如果只有当前转换单元需要这些函数),或者在用户的某个有意义的命名空间中。

但是,在最近阅读本文之后: http//www.gotw.ca/gotw/084.htm我发现自己质疑语法含义。

语法含义可以在编写良好的C ++库中随处可见:C ++在所有地方都使用免费函数。 这对于具有OOP背景的人来说是不寻常的,但它是C ++中的最佳实践。 例如,考虑STL标头<algorithm>

因此,使用点符号成为规则的例外,而不是相反。

请注意,其他语言选择其他方法; 这导致在C#和VB中引入了“扩展方法”,允许模拟静态函数的方法调用语法(即你想到的正是这样)。 然后,C#和VB是严格的面向对象语言,因此对方法调用使用单个符号可能更重要。

除此之外,函数总是属于命名空间 - 虽然我自己偶尔会违反这个规则(但只在一个编译单元中,即我相当于main.cpp ,它不起作用)。

就个人而言,我喜欢自由功能的可扩展性。 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;
}

现在考虑使用free size函数的代码:

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)而无需关心类型所在的命名空间(取决于参数类型,编译器会计算出命名空间本身),而无需关心幕后发生的事情。 考虑使用像beginend一样的自由函数。 这正是boost::range 所做的

可以使用单一语法,但可能不是您喜欢的语法。 不要在类范围内放置一个insert(),而是将其作为类的朋友。 现在你可以写了

mystring s;
insert(s, "hello");
insert(s, other_s.begin(), other_s.end());
insert(s, 10, '.');

对于任何非虚拟的公共方法,它等同于将其定义为非成员友元函数。 如果混合点/无点语法困扰你,那么一定要使这些方法成为友方函数。 没有区别。

将来我们也可以编写这样的多态函数,所以也许这是C ++方式,而不是人为地试图强制自由函数进入点语法。

如果你想保留点符号,但也需要单独的函数,这些函数不需要成为类中的朋友(因此他们无法访问私有成员从而破坏封装),你可能会编写一个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。

是的,它们应该是全局的或命名空间范围的。 非成员非朋友函数在C#中看起来更漂亮,它们使用点表示法(它们被称为扩展方法 )。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM