提问人:rfrankmcm 提问时间:8/7/2023 最后编辑:rfrankmcm 更新时间:8/8/2023 访问量:88
使用运算符重载时难以分离命名空间中类的标头/源
Difficulty separating header/source of a class within a namespace while using operator overload
问:
我在标头/源之间分离类似乎有效,直到我尝试在命名空间中使用类的声明。我相信这与指定命名空间中运算符的作用域有关,但我不知道以这种方式正确重载运算符的语法。
NameTest.h
#pragma once
#include <iostream>
namespace testspace
{
class Test
{
private:
int number{};
public:
friend std::ostream& operator<< (std::ostream& out, const Test& test);
};
}
NameTest.cpp
#include "NameTest.h"
using namespace testspace;
std::ostream& operator<< (std::ostream& out, const Test& test)
{
out << test.number;
return out;
}
我知道它在标题中是这样解决的......
(std::ostream &testspace::operator<< (std::ostream &out, const testspace::Test& test);
...我认为这与范围解析有关,但我不知道这是否是我实现命名空间、运算符或类本身的方式有问题。我至少知道,当我从命名空间中删除类声明时,它工作正常。它是否与 .cpp 中的范围解析有关?我将如何实现它?
提前致谢。
编辑:test.number 在运算符重载的定义中是无法访问的......
答:
当您在源文件中说时,这只是从命名空间导入名称以进行名称查找。如果你想在命名空间中添加一些东西,比如你的 ,那么你需要用using namespace testspace;
operator<<
namespace testspace {
然后添加运算符的实现,然后再次关闭命名空间
}
您可以根据需要/需要多次打开命名空间进行添加,跨多个文件或在同一文件中。这都是添加剂。
算子函数的特殊状态
有一个微妙的原因,必须在命名空间内进行定义。它与赋予类中声明的函数的特殊状态有关。通常,必须使用 .但是,我们通常不会对运营商这样做。相反,我们只是使用它们。operator<<
testspace
operator
dot operator
这之所以有效,是因为在类中声明的运算符会自动在声明类的作用域中可见。这是链接器在需要为您的好友运算符查找定义时查找的位置。
您已经告诉链接器,您的运算符是在命名空间中定义的,但是您在文件中定义的运算符驻留在“文件级别”。testspace
NameTest.cpp
修复是 @Jesper Juhl 给出的修复方法。将定义移动到 namespace 中。testspace
// NameTest.cpp
#include "NameTest.h"
using namespace testspace;
namespace testspace
{
std::ostream& operator<< (std::ostream& out, const Test& test)
{
out << test.number;
return out;
}
}
矫枉过正证明了这一点
这里有一点矫枉过正,证明了这一点。如果您决心将 的定义保留在“文件级别”,这就是您会做的事情。operator<<
// NameTest.h
#pragma once
#include <iostream>
namespace testspace
{
// Forward declaration tells everyone that class Test
// resides in namespace testspace.
class Test;
}
// The operator declared here is declared at the "file-level".
// Its right operand, however, will be found in namespace testspace.
std::ostream& operator<< (std::ostream& out, const testspace::Test& test);
namespace testspace
{
class Test
{
private:
int number{};
public:
// The friend declared here uses namespace scoping to indicate
// that operator<< is defined at "file-level". Note the :: right
// before the keyword operator.
friend std::ostream& ::operator<< (std::ostream& out, const Test& test);
};
}
// NameTest.cpp
#include "NameTest.h"
using namespace testspace;
// This operator is defined at the "file level."
// The right operand must be scoped to namespace testspace.
std::ostream& operator<< (std::ostream& out, const testspace::Test& test)
{
out << test.number;
return out;
}
该程序运行,本答案中给出的其他程序也是如此。我使用以下输出的驱动程序对它们进行了测试。t : 0
// main.cpp
#include <iostream>
#include "NameTest.h"
int main()
{
testspace::Test t;
std::cout << "t : " << t << "\n\n";
return 0;
}
隐藏的朋友替代品
然而,总的来说,我不喜欢上面给出的任何一个答案。相反,我会使用“隐藏的朋友”成语。该方法只是将 的定义移动到类本身中。operator<<
您可以在我对 StackOverflow 问题的回答中了解有关隐藏好友的更多信息。
我经常是操作员,但这不是绝对必要的。inline
// NameTest.h
#pragma once
#include <iostream>
namespace testspace
{
class Test
{
private:
int number{};
private:
inline friend auto operator<< (std::ostream& out, const Test& test)
-> std::ostream&
{
out << test.number;
return out;
}
};
}
为了避免混淆,我通常将我隐藏的朋友声明为 .然而,在这个例子中,我做到了.事实上,可以是 ,也可以是 。哪个并不重要。友谊与访问说明符无关。public
private
access specifier
public
private
protected
正在定义的隐藏好友不是类的成员。它是一个运算符,在类外部的作用域中可见,并且实际上驻留在该运算符中。对于不是类成员且驻留在类外部的函数,谈论 、 和 是没有意义的。public
private
protected
顺便说一句,如果您使用 hidden-friend 成语,请不要忘记删除 from file 的定义。operator<<
NameTest.cpp
旁注:
- 当您需要具有长名称的命名空间时,请考虑使用“命名空间别名”。例如,在这里,我可能会为 创建别名。
ts
testspace
namespace ts = testspace;
- 大多数情况下,您不想使用“using 指令”,例如 和 。只需写出您需要的完全范围的名称即可。例如,给定命名空间别名,它很容易编写,依此类推。
using namespace testspace
using namespace std
ts
ts::Test
评论
friend ::operator<<
NameTest.cpp
operator<<
friend
NameTest.h
NameTest.h
friend ::operator<<
评论
using namespace X;
只是添加一个命名空间以进行查找。要在命名空间中定义某些内容,您需要namespace X { ... }
namespace testspace { std::ostream& operator<< (std::ostream& out, const Test& test) { ... } }
在源文件中 - 就像在头文件中一样。namespace foo {