关于C ++标准:
std::function
是否使用union
数据类型在不同的函数指针类型之间进行转换(例如,将非静态成员函数指针转换为非成员函数指针)? union
数据类型但不进行强制转换(类型擦除)。 undefined behavior
?我想是的。std::function
的代码的情况下实施undefined behavior
? 以下是我的问题:
我们有时必须根据C ++标准编写具有undefined behavior
的代码(但对于特定的C ++编译器,例如GCC或MSVC,它们有defined behavior
吗?< / p>
这是否意味着我们不能/不应该阻止undefined behavior
我们的C ++代码?
答案 0 :(得分:41)
没有人强迫你写任何东西,所以没有人强迫你编写调用UB的代码。
对于标准库,它的代码可以自由地包含它想要的任何非便携行为 - 哎呀,它甚至可以用另一种语言编写,编译器通过神奇的独角兽创建绑定;重要的是它的行为符合规范。
考虑到这一点,显而易见的是,在某种程度上标准库必须超出标准 - 制作系统调用,与硬件交谈,......标准甚至没有考虑到,并且通常深度针对特定平台。例如,在64位Linux上,您可以使用内联汇编执行系统调用(通过sysenter
指令) - 标准不禁止这一点,它并不强制要求每个平台都必须像这样。
至于具体的例子,我没有看到任何UB - 标准规定使用的union
- 即只从您写入的最后一个成员读取(因此字段{ {1}})。
答案 1 :(得分:10)
__gnu_cplusplus_builtin_std_function__
来实现。问题的其余部分是不适当的,在评论中提到。
这是信封背面std::function
的基本模型,没有演员或工会或AFAICT任何远程危险的东西。当然不是真正的std::function
的所有功能,但这只是一些技术工作的问题。
#include <memory>
#include <iostream>
#include <type_traits>
template <typename R, typename ... Args>
struct CallBase
{
virtual R operator()(Args... args) = 0;
virtual ~CallBase() {}
};
template <typename R, typename ... Args>
struct FunCall : CallBase<R, Args...>
{
virtual R operator()(Args... args) { return f(args...); }
R(*f)(Args...);
FunCall(R f(Args...)) : f(f) {}
};
template <typename Obj, typename R, typename ... Args>
struct ObjCall : CallBase<R, Args...>
{
virtual R operator()(Args... args) { return o(args...); }
Obj o;
ObjCall(Obj o) : o(o) {}
};
template <typename R, typename ... Args> struct MemFunCall;
template <typename R, typename Cl, typename ... Args>
struct MemFunCall<R, Cl, Args...> : CallBase<R, Cl, Args...>
{
typedef typename std::remove_reference<Cl>::type Rcl;
virtual R operator()(Cl c, Args... args) { return (c.*f)(args...); }
R (Rcl::*f)(Args...);
MemFunCall(R (Rcl::*f)(Args...)) : f(f) {}
};
template <typename Fn> class Function;
template <typename R> struct Function<R()>
{
std::unique_ptr<CallBase<R>> fn;
R operator()() { return (*fn)(); }
Function(R (*f)()) : fn(new FunCall<R>(f)) {}
template<typename Obj>
Function(Obj o) : fn(new ObjCall<Obj, R>(o)) {}
};
template <typename R, typename Arg1, typename ... Args>
struct Function<R(Arg1, Args...)>
{
std::unique_ptr<CallBase<R, Arg1, Args...>> fn;
R operator()(Arg1 arg1, Args... args) { return (*fn)(arg1, args...); }
Function(R (*f)(Arg1 arg1, Args...)) :
fn(new FunCall<R, Arg1, Args...>(f)) {}
template<typename T>
Function(R (T::*f)(Args...)) :
fn(new MemFunCall<R, Arg1, Args...>(f)) {}
template<typename Obj>
Function(Obj o) : fn(new ObjCall<Obj, R, Arg1, Args...>(o)) {}
};
struct Foo
{
static void bar (int a) { std::cout << "bar " << a << std::endl; }
int baz (const char* b) { std::cout << "baz " << b << std::endl; return 0; }
void operator()(double x) { std::cout << "operator() " << x << std::endl; }
};
int main ()
{
Function<void(int)> f1(&Foo::bar);
f1(3);
Foo foo;
Function<int(Foo&, const char*)> f2(&Foo::baz);
f2(foo, "whatever");
Function<void(double)> f3(foo);
f3(2.75);
}
答案 2 :(得分:5)
GNU Compiler Collection的std :: function是否使用union数据类型 在不同的函数指针类型之间转换(例如转换 非静态成员函数指向非成员函数指针)?一世 这么认为。
不,它使用类型擦除。它是函数对象的一种variant
。
在不同的函数指针之间进行转换是不确定的行为 类型(在C ++或C ++ 11标准中)?我想是的。
它不一定是,你可以将一个函数指针转换为另一个函数指针,并将成员函数指针转换为另一个成员函数指针。
是否可以在不使用任何代码的情况下实现std :: function 哪个行为不明确?我不这么认为。我正在谈论 此
是的,你可以,有很多例子,这很容易,这样做会教会你很多关于c ++的事情:
http://probablydance.com/2013/01/13/a-faster-implementation-of-stdfunction/ https://codereview.stackexchange.com/questions/14730/impossibly-fast-delegate-in-c11 http://avdgrinten.wordpress.com/2013/08/07/c-stdfunction-with-the-speed-of-a-macro/
但是,随着时间的推移,您会发现在高层次思考和建模的能力比了解特定语言的细节更重要。
答案 3 :(得分:4)
在极少数情况下,可以自愿使用未定义的行为,即满足以下所有条件时:
基本上,如果你的编译器确实记录了标准表示未定义的行为,那么如果你不编写可移植的代码,就可以依赖它。
当然编译器,编译标志,代码和目标平台会随着时间的推移而发生变化,因此很少有好主意。 需要了解的重要一点是,C ++标准只定义了跨平台C ++代码的外观。它没有指定更多(例如,它没有指定实现),只是指定它不能指定可移植行为的位置。
因此,如果您编写可移植代码或遵循该标准的代码,则永远不必利用未定义的行为。
此外,大多数未定义的行为只是在运行时检查成本很高的使用错误(比如检查数组边界,如果需要,可以执行此操作,但标准不会强制编译器执行此操作)。因此,在某些情况下添加检查以避免未定义的行为可能有用,但最好不要在开头编写代码。这是强类型检查对大型代码库有帮助的原因之一,也是为什么静态分析最近引起了很多关注的原因。它们确实有助于防止编译一些可以在编译时检查它们将会出现问题的代码。
答案 4 :(得分:2)
C ++标准库是实现的一部分。因此,如果您在自己的用户代码中编写它,C ++标准库可能包含将具有未定义行为的源代码,但实现保证它按照C ++标准的定义工作。如果它没有按照定义工作,那么这不是未定义的行为,这是实现中的一个错误。
代码不具有&#34;未定义的行为&#34;,它具有未被C ++标准定义的行为&#34;。例如,有许多Posix函数由Posix标准定义。如果您的实现说&#34;此实现遵循C ++标准和Posix标准&#34;,那么使用不具有C ++标准定义的行为的Posix函数是可以的,因为它们已经在您的实现上定义了行为(可能不是另一个不兼容Posix的)。
您可能听说未定义的行为可能会格式化您的硬盘(和其他讨厌的东西)。但反过来说,因为&#34;格式化硬盘&#34;在C ++标准中没有提到任何地方,如果格式化你的硬盘驱动器是你的程序实际应该做的事情,那么根据C ++标准,你将不得不做一些未定义的行为。
显然,您需要一些很好的理由在代码中调用未定义的行为。由于明显的危险(不同的行为可能,优化编译器可能产生令人讨厌的行为,巨大的可移植性问题),没有非常强有力的理由的任何未定义的行为是一个非常糟糕的迹象。
答案 5 :(得分:1)
使用POSIX API访问动态链接函数的每个程序都必须依赖于C ++标准未定义的行为:特别是将dlsym
返回的void*
转换为函数指针。当然,&#34;按预期工作&#34;是实现未定义行为的一种方法,POSIX标准要求在所有兼容平台上定义强制转换。
答案 6 :(得分:0)
fast inverse square root最快的面值实现涉及未定义的行为。更符合要求的实施可能需要additional copy。鉴于快速平方根的存在理由在于快,根据情况,这可能恰好符合“需要”。然而,现代优化器具有这样的魔法能力,如果合规版本被悄然转变为最佳形式,我不会感到惊讶。