C/C++链接规范

8

当调用像copy_if、transform等C++算法时,这些算法的最后一个参数需要一个一元或二元函数,那么我能否传递一个C库函数,例如atoi或tolower。

例如,下面的调用可以正常工作并给出正确的输出(在ideone上尝试过)

1) transform (foo, foo+5, bar, atoi);
2) transform (foo, foo+5, bar, ptr_fun(atoi));
3) transform(s.begin(),s.end(),s.begin(), static_cast<int (*)(int)>(tolower));

这种用法是否保证适用于所有C++编译器?

《C++编程思想》一书提到:“这适用于某些编译器,但不是必需的。”原因是(据我了解)transform是C++函数,期望其最后一个参数具有相同的调用约定。

该书还提出了一个解决此问题的方法,即创建一个包装函数,如在单独的cpp文件中创建此函数,并不包括iostreams头文件。

// tolower_wrapper.cpp
string strTolower(string s) {
  transform(s.begin(), s.end(), s.begin(), tolower);
  return s;
} 

这很好运行,但我不明白这如何解决调用约定问题? transform仍然是一个C++函数,而tolower仍然是strTolower中的一个C函数,那么这里如何处理不同的调用约定。

这与链接有什么关系? - dyp
这个应该总是有效的..至少在我看来是这样的..我在Linux Mint、MacOS Leopard和Windows 8.1上尝试了以下内容:在Clang++、G++、MSVC2010、MSVC2012、MSVC2013和XCode(Obj-C++)中都可以工作。 - Brandon
@dyp 这就是我想要理解的。《Thinking in C++》一书中给出的确切解释是:“尽管有些晦涩,但库实现允许对从C语言继承而来的函数进行“C链接”(这意味着函数名不包含所有正常C++函数所包含的辅助信息)。如果是这种情况,则强制转换失败,因为transform是一个C++函数模板,并且期望其第四个参数具有C++链接 - 而强制转换不允许更改链接。” 这特别是针对上面的例子3。 - irappa
是的,我刚刚读到了那个 ;) 但链接性是关于名称的可见性(据我所知..),因此“强制转换不允许更改链接性”是模糊的,因为强制转换与名称无关。也许作者想说一些关于调用约定的事情? - dyp
C++11的解决方法是使用lambda表达式:transform(begin(s), end(s), begin(s), [](char c){ return tolower(c); }); - dyp
显示剩余3条评论
1个回答

2
首先需要注意的是,虽然这不属于你的问题,但对于阅读此内容的人可能会有所帮助,算法可以接受函数指针或函数对象作为参数。
函数指针就是指向函数的指针,该函数期望采用特定的参数集并返回特定类型。
函数对象是一个已重载 operator() 的类实例。
扩展算法模板时,编译器将能够看到哪种情况适用,并生成适当的调用代码。
在使用C函数作为算法中的二元函数时,您提供的是函数指针。您可以从C++中调用C函数,只要它声明为 extern C {...}.
许多编译器都附带了C库函数的头文件,其中包括以下内容:
#ifdef  __cplusplus
extern "C" {
#endif

/* function declarations here */

#ifdef  __cplusplus
}
#endif

如果你从一个C++程序中包含一个C库头文件,其中包含的函数将会神奇地对你可用。然而,这一部分并不被标准保证,这就是为什么你的书指出它可能无法在所有编译器上工作的原因。

另一个棘手的问题是,你不被允许将函数指针转换为具有不同语言链接的类型,在你的一些示例中,你正在这样做,尽管一些编译器似乎仍然允许这样做 - 例如参见这个GCC Bug

另一个需要注意的问题,特别是对于tolower这样的函数,是一些C库函数的名称也是C++ std库中的函数或模板的名称。例如,名称tolower也在<locale>中定义。这种情况在这个GCC bug report中进行了讨论。使用一个包装器,在不包括冲突声明的单独编译单元中编译,将解决这个问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接