使用运算符重载时难以分离命名空间中类的标头/源

Difficulty separating header/source of a class within a namespace while using operator overload

提问人:rfrankmcm 提问时间:8/7/2023 最后编辑:rfrankmcm 更新时间:8/8/2023 访问量:88

问:

我在标头/源之间分离类似乎有效,直到我尝试在命名空间中使用类的声明。我相信这与指定命名空间中运算符的作用域有关,但我不知道以这种方式正确重载运算符的语法。

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 在运算符重载的定义中是无法访问的......

C++ 作用域 头命名空间

评论

1赞 john 8/7/2023
using namespace X;只是添加一个命名空间以进行查找。要在命名空间中定义某些内容,您需要namespace X { ... }
0赞 Jesper Juhl 8/7/2023
namespace testspace { std::ostream& operator<< (std::ostream& out, const Test& test) { ... } }在源文件中 - 就像在头文件中一样。
0赞 rfrankmcm 8/7/2023
@heap不足 - 那么我会在标头和源代码中遇到“namespace testspace {”时遇到任何问题吗?还是只是附加命名空间中存在的内容?
0赞 Jesper Juhl 8/7/2023
您可以打开一个命名空间,在不同的文件中根据需要多次向其添加内容。namespace foo {
0赞 rfrankmcm 8/7/2023
这对Jesper有帮助,谢谢!

答:

4赞 Jesper Juhl 8/7/2023 #1

当您在源文件中说时,这只是从命名空间导入名称以进行名称查找。如果你想在命名空间中添加一些东西,比如你的 ,那么你需要用using namespace testspace;operator<<

namespace testspace {

然后添加运算符的实现,然后再次关闭命名空间

}

您可以根据需要/需要多次打开命名空间进行添加,跨多个文件或在同一文件中。这都是添加剂。

1赞 tbxfreeware 8/7/2023 #2

算子函数的特殊状态

有一个微妙的原因,必须在命名空间内进行定义。它与赋予类中声明的函数的特殊状态有关。通常,必须使用 .但是,我们通常不会对运营商这样做。相反,我们只是使用它们operator<<testspaceoperatordot operator

这之所以有效,是因为在类中声明的运算符会自动在声明类的作用域中可见。这是链接器在需要为您的好友运算符查找定义时查找的位置。

您已经告诉链接器,您的运算符是在命名空间中定义的,但是您在文件中定义的运算符驻留在“文件级别”。testspaceNameTest.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;
        }
    };
}

为了避免混淆,我通常将我隐藏的朋友声明为 .然而,在这个例子中,我做到了.事实上,可以是 ,也可以是 。哪个并不重要。友谊与访问说明符无关。publicprivateaccess specifierpublicprivateprotected

正在定义的隐藏好友不是类的成员。它是一个运算符,在类外部的作用域中可见,并且实际上驻留在该运算符中。对于不是类成员且驻留在类外部的函数,谈论 、 和 是没有意义的。publicprivateprotected

顺便说一句,如果您使用 hidden-friend 成语,请不要忘记删除 from file 的定义。operator<<NameTest.cpp

旁注:

  1. 当您需要具有长名称的命名空间时,请考虑使用“命名空间别名”。例如,在这里,我可能会为 创建别名。tstestspace
namespace ts = testspace;
  1. 大多数情况下,您不想使用“using 指令”,例如 和 。只需写出您需要的完全范围的名称即可。例如,给定命名空间别名,它很容易编写,依此类推。using namespace testspaceusing namespace stdtsts::Test

评论

0赞 Iron Fist 8/8/2023
在 NameTest.h 中,您的意思是 as 命名空间范围而不是您提到的文件级吗?friend ::operator<<
0赞 tbxfreeware 8/8/2023
@Iron 拳头:是的。在 中,在“文件级别”定义。在 的声明中,我使用命名空间范围来指示可以找到该定义的命名空间。在“文件级”定义的名称有时被称为在“全局”命名空间中。NameTest.cppoperator<<friendNameTest.h
0赞 Iron Fist 8/8/2023
然后请更新您在文件中的评论,在 ,这就是我在这里的评论NameTest.hfriend ::operator<<
0赞 tbxfreeware 8/8/2023
@Iron 拳头:完成。