为什么重载运算符不能被定义为类的静态成员?

时间:2012-08-10 01:04:43

标签: c++ operator-overloading static-methods

C ++语法允许在struct / class中定义重载运算符,如:

struct X
{
   void operator+(X);
}

或在struct / class之外,如:

void operator+(X, X);

但不是

struct X
{
   static void operator+(X, X);
}

有没有人知道这个决定的理由?为什么不允许第三种形式? (MSVC给出语法错误)。也许这背后有一些故事?

P.S。同时存在第一个和第二个定义会产生歧义:

1>CppTest1.cxx
1>c:\ballerup\misc\cf_html\cpptest1.cxx(39) : error C2593: 'operator +' is ambiguous
1>        c:\ballerup\misc\cf_html\cpptest1.cxx(13): could be 'void B1::operator +(B1 &)'
1>        c:\ballerup\misc\cf_html\cpptest1.cxx(16): or       'void operator +(B1 &,B1 &)'
1>        while trying to match the argument list '(B1, B1)'

我不明白为什么这种模糊性比1,3或2,3更好。

8 个答案:

答案 0 :(得分:17)

我对这个概念的任何C ++讨论都没有具体的知识,所以可以随意忽略它。

但对我来说,你已经向后提出了问题。问题应该是“为什么这个语法应该允许?”

它在当前语法上完全没有任何优势。非静态成员函数版本与建议的静态版本具有相同的私有成员访问权限。因此,如果您需要访问私有实现它,只需将其设置为非静态成员,就像您通常对类的大多数成员一样。

它不会更容易实现非对称运算符(即:operator+(const X &x, const Y &y))。如果你需要私人访问来实现这一点,你仍然需要在其中一个类中为他们提供朋友声明。

所以我会说它不存在的原因是它不是必要的。在非成员函数和非静态成员之间,涵盖了所有必要的用例。


或者,换句话说:

自由函数可以执行静态函数系统可以执行的所有操作,和更多

通过使用自由函数,您可以为模板中使用的运算符进行参数依赖查找。你不能用静态函数做到这一点,因为那些必须是特定类的成员。并且您不能从类外部添加到类,而您可以添加到命名空间。因此,如果您需要将运算符放在特定的命名空间中以使某些ADL代码有效,则可以。你不能用静态函数操作符来做到这一点。

因此,自由函数是您提出的静态函数系统将提供的所有内容的超集。由于允许它没有任何好处,因此没有原因允许它,因此不允许这样做。


  

哪能使用仿函数而无需实例化它们?

这是一个矛盾。 “仿函数”是“函数对象”。类型不是对象;因此,它不能是一个算子。它可以是一种类型,在实例化时,将生成一个仿函数。但这种类型本身不会是一个算子。

此外,能够声明Typename::operator()静态并不意味着Typename()会做你想要的。该语法已经具有实际意义:通过调用默认构造函数来实例化Typename临时。

最后,即使所有都不是的情况,实际上会有什么好处?大多数采用某种类型的可调用函数的模板函数与函数指针和函子一样工作。你为什么要限制你的界面,不仅仅是限制玩家,而是限制不能拥有内部数据的算子?这意味着你将无法通过捕获lambdas等等。

一个不可能包含国家的仿函数有什么用呢?为什么要强制用户传递没有状态的“仿函数”?为什么要阻止用户使用lambdas?

所以你的问题来自一个错误的假设:即使我们拥有它,它也不会给你你想要的东西。

答案 1 :(得分:14)

因为没有一个明显的语法来调用这样的运算符,这意味着我们必须要构成一些东西。请考虑以下变量:

X x1;
X x2;

现在,让我们假装我们正在使用普通成员函数而不是运算符 - 假设我在示例中将operator+更改为plus

三个电话中的每一个看起来都是:

x1.plus(x2);
plus(x1, x2);
X::plus(x1, x2);

现在,当使用+进行操作员调用时,编译器如何知道在X范围内查找运算符?它不能用于普通的静态成员函数,并且操作符没有特殊的分配来消除歧义。

现在考虑一下两者你的程序中声明的第二个和第三个表单。如果你说x1 + x2,编译器必须总是选择自由函数,否则调用将是不明确的。唯一真正的替代方案就是像x1 X::+ x2那样看起来很难看。鉴于这一切,我确信标准委员会决定简单地禁止静态成员版本,因为它可以完成的任何事情都可以通过朋友免费功能完成。

答案 2 :(得分:2)

静态成员函数可用于帮助类的实用程序,但由于某种原因不是成员。很容易想象在表示为静态类成员函数的实用程序中,拥有运算符可能很有用。

当然,如果某个重载运算符将类C作为其主要参数,则没有充分理由希望它成为类C的静态成员。它可以只是一个非静态成员,因此它获得了论据含蓄地说。

但是,C类的静态成员可能是在C以外的某些类上重载的操作符。

假设存在文件范围operator ==(const widget &, const widget &);。在我的squiggle课程中,我正在使用widget个对象,但希望对它们进行不同的比较。

我应该可以为自己制作static squiggle::operator == (const widget &, const widget &);

从课程范围来看,这很容易调用:

void squiggle::memb(widget a, widget b)
{
   if (a == b) { ... } // calls static == operator for widgets
}

从类范围之外我们只能使用显式范围解析结合显式运算符调用语法来调用它:

void nonmemb(widget a, widget b)
{
   a == b;  // calls the widget member function or perhaps nonstatic operator
   squiggle::operator ==(a, b); // calls squiggle class' utility
}

这不是一个坏主意。此外,我们可以使用常规重载函数来执行此操作,而不是使用运算符。如果使用compare函数对小部件进行比较,则可能会有非成员comparewidget::compare,并且squiggle::compare可能会widgets }}

因此,C ++不支持的唯一方面是与运算符一起使用语法。

或许它不是一个足够有用的想法来保证支持(到目前为止!)毕竟,这不是允许对C ++程序进行一些革命性重组的东西。但它会修复语言的不完整性。

此外,认为运算符newdelete的类重载是隐式静态的!因此,不完整性已经有一点例外。

答案 3 :(得分:1)

嗯......我正在考虑一个静态的operator(),它会隐含地删除所有构造函数......这会给我们带来类型的函数。有时我希望我们在C ++中使用它。

答案 4 :(得分:0)

这可能是原因。

因为每个operator需要一个或多个operands。因此,如果我们将其声明为static,那么我们无法使用对象(操作数)调用它。

为了在一些只是一个对象的操作数上调用它,该函数必须是非静态的。

以下是在执行函数重载时必须满足的条件。

  • 必须至少有一个用户定义类型的操作数。

假设我们将运算符重载函数声明为static。 那么以上所有条件中的第一个将不会得到满足。

另一个原因是,在静态函数内部,我们只能访问静态数据成员。但是在进行运算符重载时,我们必须访问所有数据成员。因此,如果我们将操作符重载函数声明为静态,则无法访问所有数据成员。

因此,运算符重载函数必须是non-static member function

但有一个例外。

如果我们使用友元函数进行运算符重载,则可以将其声明为静态。

答案 5 :(得分:0)

我不知道允许静态运算符+可能导致的任何直接缺陷(可能会考虑足够长的时间会产生一些理论)。但我认为至少Bjarne Stroustrup宣称的“不用你不用的东西”的原则已经足够好了。 除了更复杂的语法之外,如果允许静态运算符,你将获得什么(你必须在所有地方写“X :: operator +”而不仅仅是“+”)?

答案 6 :(得分:0)

基本上,类成员静态运算符不会通过非静态成员购买任何东西。

为类定义的任何运算符都必须至少使用该类类型的一个参数。

成员运算符以隐式this参数的形式获取该参数。

非成员运算符具有该类类型的显式参数。

操作员功能的操作员界面无关紧要;当我们调用a + b时,它会生成代码以通过a参数或显式声明的参数传递this。因此,对于如何使用运算符,我们没有表现出静态与非静态的细微差别。

假设突然引入了一个要求,即最新的ISO C ++必须支持静态成员运算符。 匆忙,可以根据以下模式通过源到源重写来实现此要求:

static whatever class::operator *(class &x) { x.fun(); return foo(x); }

-->

whatever class::operator *() { (*this).fun(); return foo(*this); }

-->

whatever class::operator *() { fun(); return foo(*this); }

编译器将static成员运算符重写为非静态,删除最左边的参数,并且(使用适当的词汇卫生和影子)用表达式*this替换对该参数的所有引用(不必要的其用途可以省略。)

这种转换很简单,可以依赖程序员首先编写代码。

static运算符函数定义机制功能不强。例如,它不能是virtual,而非静态的可以是。{/ p>

答案 7 :(得分:0)

首先,我不知道为什么C ++中不允许使用静态运算符。作为一名Python程序员,我看到了一些使用@classmethod方法的API灵活性的出色示例,这些方法称为Class.method,似乎没有人遭受这种事情的困扰。

我的猜测是,在C ++中,这可能是与语言设计有关的东西,因为至少我看不到其他阻止这种情况发生的东西。

好吧,即使您不能合法地这样做,也可以使用#define和运气来作弊) 免责声明! :也许您不应该在家中这样做,但这取决于您


#include <iostream>

// for demonstration purposes
// no actual array implementation
class Array
{
public:

  Array() { std::cout << "Array() created\n"; }

  Array operator()()
  {
    std::cout << "surprising operator() call\n";
    return Array();
  }

  int operator[] (int elem_count)
  {
    return elem_count;
  }

};

#define Array Array()

int main()
{
  // this is not a static method, right, but it looks like one. 
  // and if you need the static-method syntax that bad, you can make it. 
  auto arr = Array[7]; // implicitly acts as Array()[N]
  auto x = Array(); // delegate all construction calls to Array.operator()
  std::cout << arr;
}


因此,我认为您可能会以这种方式重载其他一些运算符,并使它们在语法上看起来像是静态的。