c++ 在使用运算符重载时,很难在命名空间内分隔类的头/源

mm5n2pyu  于 2023-08-09  发布在  其他
关注(0)|答案(2)|浏览(87)

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

#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中的范围解析有关吗?我该如何实现呢?
先谢了。
EDIT:test.number在操作符重载的定义中是不可访问的…

voase2hg

voase2hg1#

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

namespace testspace {

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

}


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

bpsygsoo

bpsygsoo2#

运算符函数的特殊状态

operator<<的定义必须在名称空间testspace中进行,这是一个微妙的原因。它与类中声明的operator函数的特殊状态有关。通常,类的成员必须使用dot operator引用。然而,我们通常不对操作符执行此操作。相反,我们只是使用它们。
这是因为在类中声明的运算符在声明该类的作用域中会自动变为可见。当链接器需要为您的友元运算符查找定义时,它将在此位置查找。
您已经告诉链接器,您的运算符是在命名空间testspace中定义的,但是您在文件NameTest.cpp中定义的运算符位于“文件级”。
修复程序是由@Jesper Juhl提供的。将定义移至名称空间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可以是publicprivateprotected。哪一个都无所谓。友谊与访问说明符无关。
被定义的隐藏友元 * 不是类的成员。* 它是一个在类之外的范围中可见的运算符,实际上驻留在那里。对于一个不是类的成员并且驻留在类之外的函数,讨论publicprivateprotected没有意义。
顺便说一句,如果你使用了hidden-friend这个成语,别忘了从NameTest.cpp文件中删除operator<<的定义。

  • 旁注:*

1.当您需要一个长名称的名称空间时,请考虑使用“名称空间别名”。

namespace ts = testspace;


1.大多数情况下,您不希望使用“using指令”,如using namespace testspaceusing namespace std。只需写出所需的完全作用域名称。例如,如果给定名称空间别名ts,则很容易编写ts::Test,依此类推。

相关问题