【问题标题】:C++ outside-class operator implementation resolves to `namespace::operator<<` instead of `operator<<`C++ 类外运算符实现解析为 `namespace::operator<<` 而不是 `operator<<`
【发布时间】:2021-11-12 21:46:20
【问题描述】:

所以我正在为一个类写operator&lt;&lt;重载,如下所示:

#test.hh
#pragma once
#include<iostream>
namespace ns {
    struct test {
        double d;
        test(double x): d(x) {}
        friend std::ostream& operator<<(std::ostream&, const test&);
    };
}
#test.cc
#include"./test.hh"
std::ostream& operator<<(std::ostream& os, const ns::test& a) {
    os << a.d;
    return os;
}

使用以下主要测试:

#main.cc
#include"./test.hh"
#include<iostream>
int main() {
    ns::test a(12);
    std::cout << a << std::endl;
    return 0;
}

使用g++ test.cc main.cc 编译时返回错误:

/usr/sbin/ld: /tmp/cc6Rs93V.o: in function `main':                                                      
main.cc:(.text+0x41): undefined reference to `ns::operator<<(std::ostream&, ns::test const&)'           
collect2: error: ld returned 1 exit status

显然编译器将函数解析为ns::operator&lt;&lt;,而它应该调用operator&lt;&lt;。我知道 C++ 会在参数的命名空间中找到函数,但是我是否错误地实现了运算符重载? Answers like this 似乎和我有相同的实现,唯一的区别是它们都写在标题中。

我做错了哪一部分?我该如何解决这样的问题?

【问题讨论】:

  • 您是否尝试将实现放在与声明相同的命名空间中? (包裹在namespace ns { }中)
  • @JoachimIsaksson 添加命名空间包装确实解决了这个问题,但为什么呢?我的意思是如果我在test.cc 中写std::ostream&amp; ns::operator&lt;&lt;,它给了我has not been decla|~ red within ‘ns’ ... note: only here as a ‘friend’。还有像这样的运算符(operator&lt;&lt;operator+),不应该在全局范围内定义吗?因为我们只是用不同的参数重载它(这里ns::test 作为第二个参数)。
  • @Andrew.Wolphoe 朋友声明很奇怪。它们仅对某些类型的名称查找可见,而对其他类型不可见。
  • friend std::ostream&amp; operator&lt;&lt;(std::ostream&amp;, const test&amp;); 在找到声明的命名空间中声明 operator&lt;&lt;。如果你想声明一个全局的operator&lt;&lt;,你可以这样做:friend std::ostream&amp; ::operator&lt;&lt;(std::ostream&amp;, const test&amp;);,但前提是::operator&lt;&lt;的声明此时已经可见。我不推荐它,因为它会污染全局命名空间。在namespace ns 中声明和定义operator&lt;&lt;,这就是命名空间的使用方式。

标签: c++ namespaces operator-overloading header-files


【解决方案1】:

这是我对这种情况的解决方案:

#include<iostream>

namespace ns 
{
    struct test 
    {
        double d;
        test(double x) : d(x) {}
        friend std::ostream& operator<<(std::ostream&, const test&);
    };

    std::ostream& operator<<(std::ostream& os, const ns::test& a) {
        os << a.d;
        return os;
    }
}

int main() 
{
    ns::test a(12);
    std::cout << a << std::endl;
    return 0;
}

【讨论】:

  • 建议将实现与标头分开(至少我所学的和目前首选的)。在使用自动工具构建大型项目时确实有一些影响,可以为您节省大量的编译时间
  • 你理解正确! :) 只是堆栈溢出不能很好地分隔文件。 (我可能应该将它们分开,只是想显示最少的代码量)如果您真的想了解更多关于头文件与代码分离的信息,请查看抽象基类(和工厂)、依赖注入和 pimpl 模式: ) 快乐编码
【解决方案2】:

编译器使用参数相关查找 (ADL)。由于运算符的第二个操作数在名称空间 ns 中声明了类型说明符 test,因此编译器还在 test 的类定义和声明该类的命名空间 ns 中搜索该运算符。之所以找到这样的运算符,是因为它的声明存在于类测试的范围内,但未定义该运算符。所以编译器会报错。

如果您要定义运算符,那么编译器将发出另一个错误,即定义了两个运算符并且出现了歧义。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-30
    • 2016-08-30
    • 1970-01-01
    • 2012-01-22
    • 2023-03-24
    • 2012-11-06
    相关资源
    最近更新 更多