怎样让C++函数重载时连返回值类型也加入重载决议?
众所周知,C++函数重载时返回值是不参与重载决议的, 也就是说:
int lex_cast(const char*);
double lex_cast(const char*);
这样两个函数在同一个编译单元同一个 namespace 中时, 会编译报错.
怎么办呢?
一个小技巧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <iostream> #include <string> #include <boost/lexical_cast.hpp> class my_cast { const char* s; public: template<class Target> operator Target() const { return boost::lexical_cast<Target>(s); } my_cast(const char* s) : s(s) {} }; int main() { using namespace std; long long ll = my_cast("12345678910111213"); int ii = my_cast("-123"); string ss = my_cast("abcde"); double dd = my_cast(('3' + string(".2222")).c_str()); cout << ll << endl; cout << ii << endl; cout << ss << endl; cout << dd << endl; return 0; } |
小得几乎不值一提. 但是, 这段代码中, 可能有些仔细的人会提出疑问:
1 |
('3' + string(".2222")).c_str() |
这里返回的 const char* 是属于临时变量的, 而 my_cast 竟然把这个临时变量保存到它的成员, 这里能成功并无错应该只是因为被释放的 c_str() 那块内存恰好未被重新分配, 太危险了!
其实, 这没任何问题, C++ 标准规定: 临时变量的生存期是包含它的那个表达式(整个), 而非任何一个真子表达式. 仔细体味这句话吧!
上面只是在 my_cast 非模板的情况, 如果 my_cast 包含模板参数, 并且 my_cast 成员也必须是模板, 怎么办?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
template<class Source> class my_cast_imp { Source s; public: template<class Target> operator Target() const { return boost::lexical_cast<Target>(s); } my_cast_imp(const Source& s) : s(s) {} }; template<class Source> my_cast_imp<Source> my_cast2(const Source& s) { return my_cast_imp<Source>(s); } |
只需定义一个辅助模板类, 再加一个模板函数去实例化模板类.
核心思想只有一个: 使用 operator class conversion, 下面是一个比较实用的 cast, 没有用 boost::lexical_cast, 没有编译龟速的问题. (boost 过度使用 template 了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <stdlib.h> #include <string> class goldcast_imp { const char* s; int base; public: goldcast_imp(const char* s, int base) : s(s), base(base) {} operator int () const { return (int) strtol (s, NULL, base); } operator unsigned() const { return (unsigned)strtoul(s, NULL, base); } operator signed long () const { return strtol (s, NULL, base); } operator signed long long() const { return strtoll (s, NULL, base); } operator unsigned long () const { return strtoul (s, NULL, base); } operator unsigned long long() const { return strtoull(s, NULL, base); } operator float() const { return strtof(s, NULL); } operator double() const { return strtod(s, NULL); } operator long double() const { return strtold(s, NULL); } operator std::string() const { return std::string(s); } }; goldcast_imp goldcast(const std::string& s, int base = 10) { return goldcast_imp(s.c_str(), base); } goldcast_imp goldcast(const char* s, int base = 10) { return goldcast_imp(s, base); } |
此思想是错误的, lex_cast(const char*);你如何判断是调用哪一个? 即,此刻不我需要函数的返回值.
[reply]weiqubo[/reply]
啥事都不干,operator xxx 不会执行
[reply]whinah[/reply]
不会执行? 显然我调用了一个函数,你却让它也不干?当然如果我int a = lex_cast(const char*)时,这个函数又开始执行指令了,二义性消除了吗?
用返回值当作重载的判断从理论上是行不通的,否则C++设计者早就将其加入了.
[reply]weiqubo[/reply]
无语, 我对牛弹琴了
作者把重载决议通过封装推迟到赋值的时候了。然而有时我们需要返回值做主语,不得不手动来一个强制转换来激发重载决议了,建议作者把这一点加上。
作者把重载决议通过封装推迟到赋值的时候了。然而有时我们需要返回值做主语,不得不手动来一个强制转换来激发重载决议了,建议作者把这一点加上。
[reply]zy498420[/reply]
Good advice, Thank you!
[reply]whinah[/reply]
毕竟在c++的空间里,返回值除了做函数的宾语,还可以做主语。比方说我要你这个cast 返回的std::string右值通过swap成员函数赋值给另一个std::string, 而不是直接赋值。
C++ 标准规定: 临时变量的生存期是包含它的那个表达式(整个), 而非任何一个真子表达式. 这一点非常关键, 在我看到标准文本中的这句话之前, 我是不敢写这样的代码的: goldcast 可能返回一个 temp string 的 c_str()!
所以 int x = goldcast(string("123")); 是合法代码, 而 goldcast_imp gci = goldcast(string("123")); int x = gci; 是非法的, 当然这里多余 string 的构造是故意的.
[reply]whinah[/reply]
一个右值持有另一个右值的引用(或者持有另一个右值的数据成员的引用),都是安全的,不会有野引用,呵呵。除非画蛇添足的去写析构函数,那就不保证了。
[reply]zy498420[/reply]
必须保证它们都在同一个表达,跨越表达式就不能保证安全了。
[reply]whinah[/reply]
呵呵,访问右值肯定只能在一行表达式中的。始终用右值访问或引用右值,所有的一切都不会逾越这行表达式。除非用左值去访问或引用了右值。
[reply]zy498420[/reply]
逗号分隔的,也算是在同一个表达式:
const char* p;
p = string("abc").c_str(), printf("%sn", p); 这是安全的,合法的
[reply]whinah[/reply]
逗号当然也是运算符哦。只要不要过了这一行 继续使用p
开眼界了
看过您关于C++重载扩展的这篇文章,想到了很久以前遇到的一个问题:
针对所有动态库中的函数的调用,写一个测试MFC工程。
比如动态库中有一个函数int add(int a,int b),用户只用在文本框中输入int add(int 3,int 4)和该函数所在的dll路径,点击运行,马上就可以输出返回值。
我所知的调用dll中函数的方式:先定义函数指针类型:typedef int (WINAPI ADD)(int a,int b);然后定义该指针类型的指针,赋值为LoadLibrary的返回值。问题是要针对动态库中所有的函数,就涉及到很多种数据类型和参数列表组合。如何针对复杂多变的函数参数列表,定义对应的指针类型以供调用?
如今,我的理解是写一个C/C++运行时库,解析用户输入的文本字符串,动态生成一个符合条件的函数指针。但是具体怎么实现,还是没想好。有什么建议?
代码
http://code.google.com/p/febird/source/browse/trunk/febird/src/febird/lcast.hpp