Friend函数和命名空间

Friend functions and namespaces

本文关键字:命名空间 函数 Friend      更新时间:2023-10-16

我正试图重载<lt;属于用户定义命名空间一部分的类的运算符。有趣的是,如果我删除了所有的命名空间内容,程序编译和运行都没有问题,但类驻留在命名空间中的事实不知何故使文件a.cpp的编译过程失败,并出现一个错误,表示我正在尝试访问类a中的私有数据(demo.cpp编译良好(。请看一下我的三文件程序和我得到的编译错误:

demo.cpp:

#include <iostream>
#include "A.h"

int main() {
    usr::A a(4);
    std::cout << a << std::endl;

    return 0;
}

A.h:

#ifndef A_H_
#define A_H_
#include <iostream>

namespace usr {
    class A {
        private:
            int m_x;
        public:
            A(int x);
            friend std::ostream& operator<<(std::ostream& os, const usr::A& a);
    };
}
#endif // A_H_

A.cpp:

#include "A.h"

usr::A::A(int x) : m_x(x) {}
std::ostream& operator<<(std::ostream& os, const usr::A& a) {
    os << a.m_x;
    return os;
}

错误:

$ g++ -c A.cpp
In file included from A.cpp:1:0:
A.h: In function ‘std::ostream& operator<<(std::ostream&, const usr::A&)’:
A.h:10:17: error: ‘int usr::A::m_x’ is private
             int m_x;
                 ^
A.cpp:7:13: error: within this context
     os << a.m_x;
             ^

非限定友元声明总是引用最小封闭命名空间的成员。当一个类在命名空间usr中声明时,该类内的任何非限定友元声明都引用usr的成员。即,您的好友声明将usr::operator <<声明为好友。

在这种情况下,全局::operator <<仍然是非好友,这就是为什么当您尝试从::operator <<访问usr::A的私有成员时会出现错误。

如果你想让它发挥作用,你必须要么

  1. 使您的operator <<成为usr或的成员

  2. 确保友元声明通过使用限定名::operator <<显式引用全局operator <<(这还需要在尝试通过其限定名引用它之前引入::operator <<(。

我建议采取第一种方法。如果类A是命名空间usr的成员,那么最好将处理A的所有函数也声明为usr的成员。这将帮助您避免在以后的过程中出现与自变量相关的查找(ADL(的许多问题。


但是,如果出于某种原因,您需要您的operator <<继续作为全局命名空间的成员,那么以下是您必须跳过的障碍,以使其编译(合并为一个翻译单元(

// Pre-declare `usr::A` to enable pre-declaration of our `::operator <<`
namespace usr {
    class A;
}
// Pre-declare our `::operator <<`
std::ostream& operator<<(std::ostream& os, const usr::A& a);
namespace usr {
    class A {
        private:
            int m_x;
        public:
            A(int x);
            friend std::ostream& ::operator<<(std::ostream& os, const usr::A& a);
            // Friend declaration uses qualified name - it explicitly 
            // refers to `::operator <<` declared above
    };
}
usr::A::A(int x) : m_x(x) {}
std::ostream& operator<<(std::ostream& os, const usr::A& a) {
    os << a.m_x;
    return os;
}
int main() {
    usr::A a(4);
    std::cout << a << std::endl;
}

在GCC下,您必须通过一些访问修饰符(const或指针上的引用(来分离返回类型和范围解析运算符(::令牌(。

例如,这个不会在g++7.2.0:下编译

std::string f(int a);
namespace NS
{
    class C
    {
        friend std::string ::f(int a);
        // scope operator  ^^ is absolutely needed
    }
}

但这个

std::string f(int a);
namespace NS
{
    class C
    {
        friend std::string const ::f(int a);
        // see const keyword ^
    }
}

这个

std::string f(int a);
namespace NS
{
    class C
    {
        friend std::string& ::f(int a);
        // see ref symbol ^
    }
}