今天在《Exceptional C++》上看到的两个有意思的东西

82 篇文章 0 订阅

一:

对于以下代码:

/*
 * Int.h
*/
#include <ostream>

struct Int
{
    std::ostream & print(std::ostream & os) const;
    /* other codes */
};

std::ostream & operator << (std::ostream & os, const Int & i)
{
    return(i.print(os));
}

为了将编译期依存性减至最小,我们一般会用一个ostream的前置声明,来取代<ostream>头文件的包含:

/*
 * Int.h
*/
class ostream;

struct Int
{
    std::ostream & print(std::ostream & os) const;
    /* other codes */
};

std::ostream & operator << (std::ostream & os, const Int & i)
{
    return(i.print(os));
}

也许在过去是可以的(当时ostream是个类,也不在namespace std中),然而在现今它是非法的,原因有二:

1  ostream现在是在namespace std里,程序员是不允许声明namespace std任何东西的;

2  ostream现在是一个模板的typedef,它是由basic_ostream<char> typedef而成的,不仅在任何情况下前置声明basic_ostream<char>模板都会造成混乱,而且根本不可能可靠地进行前置声明,因为库的各种实现产品可能自行其事,比如加入自己额外的模板参数(在标准要求之外的),你的代码当然不会知道这些了--这是不允许程序员自己声明namespace std里元素的主要原因之一。

标准库为我们提供了头文件<iosfwd>,它包含了所有流模板(包括basic_ostream)及其标准的typedef(包括ostream)的前置声明,因此我们所需要做的只是用它替代<ostream>头文件。

/*
 * Int.h
*/
#include <iosfwd>

struct Int
{
    std::ostream & print(std::ostream & os) const;
    /* other codes */
};

std::ostream & operator << (std::ostream & os, const Int & i)
{
    return(i.print(os));
}


二:

Koenig查找(简化版):

如果提供一个类类型的函数参数,那么在查找正确的函数名时,编译器将考虑包含参数类型的名字空间内匹配的函数名。

 

接口规则:对于类X,所有函数,包括自由函数,只要符合如下条件:

1  “提及”X;

2  与X“一起提供”(在相同的头文件或在同一个namespace中)。

逻辑上都是X的一部分,因为它们构成了X接口的一部分。

 

下面是Myers例子:

namespace NS
{
    class T { };
}

void f(NS::T) { }

int main()
{
    f(NS::T()); // OK
    return(0);
}

 

namespace NS
{
    class T { };
    void f(T) { } // new function
}

void f(NS::T) { }

int main()
{
    f(NS::T()); // error: ambiguous
    return(0);
}
#include <iostream>
using namespace std;

namespace A
{
    class T { };
    
    ostream & operator << (ostream & os, const T &)
    {
        return(os << "A");
    }
}

namespace B
{
    ostream & operator << (ostream & os, const A::T &)
    {
        return(os << "B");
    }

    void g(A::T param) { cout << param; } // error: ambiguous
}

int main()
{
    return(0);
}

 

namespace A
{
    class T { };
    void f(const T &) { }
}

namespace B
{
    void f(const A::T &) { }
    void g(A::T param) { f(param); } // error: ambiguous
}

int main()
{
    return(0);
}


注意!下面这个不是Myers例子:

namespace A
{
    class T { };
    void f(const T &) { }
}

class B // B is not a namespace but a class now!
{
    void f(const A::T &) { }
    void g(A::T param) { f(param); } // OK
};

int main()
{
    return(0);
}

这里B是一个class而不是一个namespace,没有任何二义性,当编译器发现一个名为f()的成员函数,它将不会试图使用Koenig查找规则来发现自由函数。

下面的例子可以更明确的说明这一点:

namespace A
{
    class T { };
    void f(const T &) { }
}

class B // B is not a namespace but a class now!
{
    /*
     * hide other functions named 'f'
     * which in other domain
    */
    void f() { }
    void g(A::T param) { f(param); } // no matching function
};

int main()
{
    return(0);
}


再来看一个例子:

namespace A
{
    class T { };
}

void operator + (A::T, int) { }

namespace B
{
    struct U { };

    /*
     * hide other functions named 'operator +'
     * which in other domain
    */
    void operator + (U, int) { }

    /* no match for 'operator +' */
    void test(A::T param) { param + 1; }
}

int main()
{
    return(0);
}


这种结果不是我们想要的,如果我们想让 param + 1 调用正确函数,

可以用域作用符(::)明确地指示我们要调用哪个函数:

namespace A
{
    class T { };
}

void operator + (A::T, int) { }

namespace B
{
    struct U { };

    /*
     * hide other functions named 'operator +'
     * which in other domain
    */
    void operator + (U, int) { }

    /* call global 'operator +' explicitly */
    void test(A::T param) { ::operator +(param, 1); } // ok
}

int main()
{
    return(0);
}

或是用using声明:

namespace A
{
    class T { };
}

void operator + (A::T, int) { }

namespace B
{
    struct U { };

    /*
     * hide other functions named 'operator +'
     * which in other domain
    */
    void operator + (U, int) { }

    /* using declaration of global 'operator +' */
    using ::operator +;

    void test(A::T param) { param + 1; } // ok
}

int main()
{
    return(0);
}

但是,如果我们无法用上面的方法呢?

比如:test是namespace std中的一个函数,我们就没法用上面的两种方法来调用正确的operator +!


 

我们还有一个办法,那就是前面讲到的“Koenig查找”!如果我们将第一个 operator + 放入namespace A中,编译器就可以用“Koenig查找”找到正确的operator +!

namespace A
{
    class T { };

    void operator + (A::T, int) { }
}

namespace B
{
    struct U { };

    /*
     * hide other functions named 'operator +'
     * which in other domain
    */
    void operator + (U, int) { }

    /*
     * we need do nothing this time
     * Koenig search (ADL) will get A::operator +
    */
    void test(A::T param) { param + 1; } // ok
}

int main()
{
    return(0);
}


为了防止莫名其秒的函数隐藏给我们带来不必要的痛苦,我们应该将类的全部(包括与类类型相关的自由函数),放入相同的名字空间!

 

下面的代码也可以:

class T { };
void operator + (T, int) { }

namespace B
{
    struct U { };

    /*
     * hide other functions named 'operator +'
     * which in other domain
    */
    void operator + (U, int) { }

    /*
     * we need do nothing this time
     * Koenig search (ADL) will get A::operator +
    */
    void test(T param) { param + 1; } // ok
}

int main()
{
    return(0);
}


因为类T与我们都要的operator +定义在同一个文件中,也就是说它们其实是定义在同一个无名的名字空间中,所以,可以由“Koenig查找”找到这个operator +!

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值