当前位置: 首页 > news >正文

CppCon 2014 学习:Return values take a ”closure” walk

这句话 ——
“Return values take a ‘closure’ walk. How to pass return values without specifying their type”
是在讨论 如何以更灵活的方式处理函数的返回值,尤其是在不明确指定类型的情况下。这在现代 C++(特别是 C++11 及以后)中与 lambda 表达式、泛型、autodecltypestd::functionstd::any 等紧密相关。

理解要点:

“Return values take a closure walk” 是什么意思?

这是比喻性的说法,意思是:

“函数的返回值可以像闭包一样被延迟处理或传递,而不必显式说明其类型。”

比如在现代 C++ 中,我们可以用 auto 或 lambda 来捕获一个返回值、延迟执行或灵活转发:

auto result = []() {return 42;  // 类型自动推导为 int
}();

或者:

std::function<void()> f = [x = computeValue()] {std::cout << x << "\n";  // computeValue 的返回类型可能很复杂,但无需写出来
};

如何不指定返回值类型?

1. 使用 auto
auto myFunction() {return someExpression;
}

编译器会根据 someExpression 自动推导返回值类型。

2. 使用 decltype(auto)

如果你想返回精确类型(包括引用、cv 修饰符)

decltype(auto) myFunction() {return someObj.someMethod();
}
3. 使用泛型 lambda / 函数模板
template<typename Func>
auto wrapper(Func f) {return f(); // 返回值类型由 f 决定
}
4. 使用 std::function 来传递返回值行为
std::function<int()> gen = []() { return 123; };

与“闭包”有关的用法示例

auto make_adder(int x) {return [=](int y) { return x + y; };
}
auto add5 = make_adder(5);
int result = add5(10);  // 15

在这个例子中:

  • make_adder 返回一个闭包(lambda);
  • 不显式说明返回类型;
  • 返回值可携带“状态”(即 x = 5),这是闭包的核心特性。

总结

目标技术
不显式声明返回值类型auto, decltype(auto)
返回/传递 lambda 表达式闭包(closure)
延迟返回、延迟执行lambda + std::function
高度泛型、灵活接口模板函数、概念、type-erased return (std::any)
简言之:
现代 C++ 鼓励你使用 lambda(闭包)与类型推导,让返回值“自由行走”于调用链中,而不是强行给它贴上标签。这让接口更灵活、表达更清晰、代码更少。

这个例子很好地展示了 “在某个上下文环境内调用函数” 的技巧,尤其是利用了 C++ 的 lambda(闭包)和 std::function。下面帮你详细解释:

代码含义拆解

void callWithin(const std::function<void()>& fn) 
{ ScopedContext context;  // 创建一个上下文对象,构造时建立环境try { fn();  // 调用传入的函数(或闭包)} catch (SomeException& e) { // 这里可以捕获并处理异常} 
}
  • ScopedContext context;:假设这是一个 RAII 类型的对象,构造时设置一些“上下文环境”,析构时自动恢复或清理。
  • fn() 是传入的函数/闭包,调用它时处于 ScopedContext 环境中。
  • 捕获异常,避免异常泄漏或做特殊处理。
void printLine(const std::string& text) 
{ std::cout << text << "\n"; 
}

这是一个简单函数,用来打印字符串。

callWithin([](){ printLine("Hello, CppCon"); });

调用 callWithin,并传入一个 lambda,lambda 内部调用 printLine。这样就确保 printLine 是在 ScopedContext 的上下文中执行的。

为什么这么写很有用?

  1. 封装上下文管理
    例如设置线程本地变量、事务、锁、日志上下文,或者类似 COM 的 CoInitialize / CoUninitialize
    这样可以避免重复写环境准备和清理代码。
  2. 异常安全
    统一处理异常,防止崩溃,或者做日志、恢复等。
  3. 灵活性
    你可以传入任意函数或闭包,且不需要声明参数或返回值,通用性强。

这里“理解”你说的:

  • 函数参数是闭包或函数对象,通过 std::function<void()> 接收。
  • ScopedContext 是上下文管理类,通过 RAII 实现。
  • 调用函数时,自动带有上下文环境
  • 这是典型的**高阶函数(函数接收函数作为参数)**的用法。
  • 在现代 C++ 里很常见,简化资源管理和代码结构。

你写的这个模板版本其实比之前的 std::function<void()> 版本更通用、更高效。

代码解析

template <typename Callable>
void callWithin(const Callable& fn)
{ScopedContext context;  // 创建上下文,RAII 管理资源fn();                   // 调用传入的可调用对象(函数、lambda、函数对象等)
}
  • 这里用模板参数 Callable,接受任意可调用类型(函数指针、函数对象、lambda)。
  • 编译器会在调用时为不同的 Callable 生成对应的函数模板实例,避免了 std::function 的类型擦除开销,通常性能更好。
  • 使用模板还能避免 std::function 对参数类型的限制,比如传参和返回值都支持(只要调用表达式有效)。

为什么更好?

  • 泛型更强:不局限于 void(),可以传入带参数和返回值的函数(如果你调整 callWithin 让它传参)。
  • 零开销抽象:编译期内联,避免了运行时动态分发。
  • 更灵活:任何符合调用操作符的类型都能用,甚至是状态ful 的函数对象。

举个例子

template <typename Callable>
void callWithin(const Callable& fn)
{ScopedContext context;fn();
}
void foo() { std::cout << "foo called\n"; }
callWithin(foo);
callWithin([]() { std::cout << "lambda called\n"; });

都能正常编译调用。
总结:
模板版本的 callWithin 是更现代、推荐的写法。

你这里用的是虚函数接口来实现“在某个上下文中调用函数”的模式。这样设计的好处和特点:

代码示意

class ContextManager
{
public:virtual void callWithin(const std::function<void()>& fn) = 0;
};
  • callWithin 是纯虚函数,表示任何继承 ContextManager 的类都必须实现这个方法。
  • 接收一个 std::function<void()>,意味着调用者可以传递任意无参无返回值的可调用对象(函数、lambda、函数对象)。

用途和设计意图

  • 面向接口编程:通过虚函数抽象上下文调用,不同子类可以实现不同的上下文管理策略(比如事务管理、日志管理、异常捕获等)。
  • 多态调用:你可以写代码只依赖 ContextManager 接口,运行时用不同的实现实例替换,增加灵活性。
  • 函数回调封装:传入的 fn 函数体会在实现的上下文中执行,比如加锁、计时、异常处理。

举个例子

class ScopedContextManager : public ContextManager
{
public:void callWithin(const std::function<void()>& fn) override{ScopedContext context;fn();}
};

使用时:

ScopedContextManager mgr;
mgr.callWithin([](){ std::cout << "Inside context\n"; });

优缺点总结

  • 优点:
    • 通过虚函数实现运行时多态,灵活扩展不同上下文行为
    • 统一接口调用方式
  • 缺点:
    • 使用 std::function 有额外开销(类型擦除、内存分配)
    • 虚函数调用有一定性能成本(间接调用)
    • 模板版本通常更高效,但不支持运行时多态
      如果你想实现跨多个上下文管理的灵活调用,这种虚接口模式是合适的。如果性能关键,且可接受编译时确定上下文,模板版本更优。

你有个带返回值的函数 sum(double, double)

  • 想通过 callWithin 这个上下文包装函数来调用,并且获取返回值
    但是你给的例子:
double result = callWithin([](){ return sum(3.14, 2.71); });

这里的 callWithin 必须支持返回值,且能够传递这个返回值给外层。

下面是一个支持返回值的 callWithin 模板函数示例:

template<typename Callable>
auto callWithin(Callable&& fn) -> decltype(fn())
{ScopedContext context; // 设定上下文return fn();           // 调用函数,并返回结果
}

这样你就可以写:

double result = callWithin([](){ return sum(3.14, 2.71); });

说明

  • decltype(fn())callWithin 函数自动推导并返回 fn() 的返回值类型
  • 这样既能包装上下文逻辑,也能支持不同返回值类型的函数调用

你这个版本的 callWithin 是专门针对返回 double 类型的函数,写成了:

double callWithin(const std::function<double()>& fn) 
{ ScopedContext context; return fn(); 
}

特点:

  • 参数是 std::function<double()>,意味着传入的函数必须返回 double
  • 在调用函数前创建了一个 ScopedContext(假设是某种上下文管理,比如自动资源管理)
  • 调用函数并返回它的 double 返回值
    用法示例:
double sum(double a, double b) { return a + b; }
double result = callWithin([=]() { return sum(3.14, 2.71); });

这种写法限制了返回值必须是 double,如果想支持任意返回类型,建议用模板版本。

如何写一个通用的 callWithin 函数,支持传入任意返回类型的函数,并正确返回结果。

关键点解析:

  1. 模板写法,返回类型自动推断:
template <typename Callable> 
auto callWithin(const Callable& fn) -> decltype(fn())
{// 需要调用一个实现函数处理上下文callWithinImpl(fn);  // 这里会报错,原因是类型不匹配// 还需要返回调用结果
}
  • decltype(fn()) 自动推断 fn 的返回类型,方便写泛型函数。
  • 这里调用了 callWithinImpl(fn),但 callWithinImpl 只接受 std::function<void()>,传入类型不匹配导致编译失败。
  1. 辅助函数:
void callWithinImpl(const std::function<void()>& fn);
  • callWithinImpl 是已经定义好的,处理上下文和异常的函数,接受 std::function<void()>
  1. 解决办法:用局部变量保存结果
template <typename Fn> 
auto callWithin(const Fn& fn) -> decltype(fn()) 
{decltype(fn()) result{};  // 声明返回值变量// 在上下文内调用fn,保存返回值到result// ...return result;
}
  • 这里声明了一个 result 变量来保存返回值。
  • 具体调用和上下文管理的代码没写出来,需要补充。

总结:

  • 你想写一个泛型函数 callWithin,能接受任何有返回值的函数,并返回该返回值。
  • 你想把实际调用和上下文管理的代码封装到 callWithinImpl
  • callWithinImpl 只能接受无返回值的 std::function<void()>,所以你需要一个中间层来:
    • 调用 fn,拿到结果
    • 传递无返回值的函数给 callWithinImpl
    • 最后返回结果

这个版本的 callWithin 利用了闭包捕获的机制,把返回值保存到外部变量里,然后传给只能接受 void() 函数的 callWithinImpl,最后返回保存的结果。

代码结构讲解:

template <typename Fn>
auto callWithin(const Fn& fn) -> decltype(fn())
{decltype(fn()) result{};          // 先声明保存返回值的变量auto wrapperFn = [&]() -> void    // 用一个无参、无返回值的lambda包裹{result = fn();                // 在闭包内调用fn,将结果保存到result};callWithinImpl(wrapperFn);        // 调用处理上下文的实现函数,传入无返回值的lambdareturn result;                    // 返回保存的结果
}

关键点:

  • 闭包捕获 [&]wrapperFn 捕获了外部的 result 变量引用,所以可以修改它。
  • 兼容接口callWithinImpl 只能接受 std::function<void()> 类型的参数,使用无返回值lambda满足这个条件。
  • 返回值保存fn() 的返回值被存入 result,这样在 callWithinImpl 调用后还能得到正确返回值。

作用:

  • 允许 callWithin 既支持带返回值的调用,又能利用已有只接受无返回值函数的上下文管理代码(callWithinImpl)。
  • 利用闭包巧妙地“绕过”类型限制,实现灵活复用。

这里结合了成员模板函数虚函数接口,实现了灵活的调用上下文管理:

class ContextManager 
{ 
public: template <typename Fn> auto callWithin(const Fn& fn) -> decltype(fn()) { decltype(fn()) result{}; callWithinImpl([&](){ result = fn(); }); return result; } 
private: virtual void callWithinImpl(const std::function<void()>& fn) = 0; 
}; 
// usage 
double result = manager->callWithin([](){ return sum(3.14, 2.71); }); 

关键点解析:

  • callWithin 是模板函数,能接受任何可调用对象 fn,返回类型根据 fn() 自动推断(decltype(fn()))。
  • result 用来存储 fn() 的返回值。
  • 用一个捕获了 result 的无返回值 lambda 传给纯虚函数 callWithinImpl
  • callWithinImpl 是纯虚函数,由派生类实现,接受 std::function<void()>,执行具体的上下文管理(如锁定、异常捕获等)。

优点:

  • 通过模板,callWithin 支持任意返回值类型,不用事先写特定类型版本。
  • 通过虚函数,派生类可定制具体的上下文行为(比如日志、事务、异常处理等)。
  • 保持接口简洁,调用方便。

使用示例:

class MyContextManager : public ContextManager {
private:void callWithinImpl(const std::function<void()>& fn) override {// 自定义上下文,例如:try {// 进入上下文fn();// 退出上下文} catch (...) {// 处理异常throw;}}
};
MyContextManager mgr;
double result = mgr.callWithin([](){ return 3.14 + 2.71; });
#include <functional>
#include <iostream>
class ContextManager {
public:template <typename Fn>auto callWithin(const Fn& fn) -> decltype(fn()) {decltype(fn()) result{};callWithinImpl([&]() { result = fn(); });return result;}
private:virtual void callWithinImpl(const std::function<void()>& fn) = 0;
};
class MyContextManager : public ContextManager {
private:void callWithinImpl(const std::function<void()>& fn) override {// 自定义上下文,例如:try {// 进入上下文fn();// 退出上下文} catch (...) {// 处理异常throw;}}
};
int main() {MyContextManager mgr;double result = mgr.callWithin([]() { return 3.14 + 2.71; });std::cout << result << std::endl;
}

这里的关键是:你传入的 lambda 是 void() 类型的函数,没有返回值。

callWithin 模板函数能兼容这种情况,因为:

  • 如果传入的函数返回 voiddecltype(fn()) 就是 void
  • 这时,你的 callWithin 需要做特殊处理,因为不能声明 decltype(fn()) result{}(即不能声明 void 类型的变量)。

处理 void 返回类型的一种思路:

template <typename Fn>
auto callWithin(const Fn& fn) -> decltype(fn()) {if constexpr (std::is_void_v<decltype(fn())>) {// 函数返回void,不需要保存返回值callWithinImpl(fn);} else {// 函数有返回值,保存返回值decltype(fn()) result{};callWithinImpl([&]() { result = fn(); });return result;}
}

这样写:

  • 如果 fn() 返回 void,直接调用 callWithinImpl(fn),无返回值。
  • 否则,按之前方式保存并返回结果。

例子:

void printLine(const std::string& text) {std::cout << text << "\n";
}
callWithin([]() { printLine("Hello, CppCon"); });  // 调用无返回值函数

这能正常编译并运行。
如果你需要,我可以帮你写一个完整版本的 ContextManager 类实现,包含对 void 返回值的处理!

你这里的问题是:

  • decltype(fn()) 被推导为 void
  • 所以你写的 decltype(fn()) result{} 实际是 void result{},这是非法的。
  • 然后你又试图给 result 赋值和返回,编译器报错。
    这正是模板在处理返回类型为 void 的函数时常见的坑。

解决思路:

针对返回类型为 void 和非 void 做不同处理:

template <typename Fn>
auto callWithin(const Fn& fn) -> decltype(fn())
{if constexpr (std::is_void_v<decltype(fn())>) {// 返回void,不声明变量,直接调用callWithinImpl(fn);// no return statement for void} else {// 非void,声明变量保存结果decltype(fn()) result{};callWithinImpl([&]() { result = fn(); });return result;}
}
  • if constexpr 是 C++17 新特性,编译时判断分支,只会编译对应路径,避免非法代码。
  • 如果你的编译器不支持 C++17,可以用重载或者 std::enable_if 实现类似效果。

C++11/14 版本重载写法示例:

// 处理void返回值
template <typename Fn>
auto callWithinImplVoid(const Fn& fn) -> void
{callWithinImpl(fn);
}
// 处理非void返回值
template <typename Fn>
auto callWithinImplNonVoid(const Fn& fn) -> decltype(fn())
{decltype(fn()) result{};callWithinImpl([&]() { result = fn(); });return result;
}
// 主接口
template <typename Fn>
auto callWithin(const Fn& fn) -> decltype(fn())
{return callWithinImplNonVoid(fn);
}
// 针对void返回重载
template <typename Fn>
void callWithin(const Fn& fn, typename std::enable_if<std::is_void<decltype(fn())>::value>::type* = 0)
{callWithinImplVoid(fn);
}

总结:
不能直接声明 void 类型变量,针对 void 返回类型需要特别处理。
C++17 以后推荐用 if constexpr,更简洁。
C++11/14 可用模板重载或 SFINAE。

你这里用的是 类型萃取(type traits)重载,实现了针对 void 和非 void 返回类型的不同处理:

template <typename Fn>
auto callWithin(const Fn& fn) -> decltype(fn()) {return _callWithin(fn, std::is_same<decltype(fn()), void>());
}
template <typename Fn>
void _callWithin(const Fn& fn, std::true_type) {callWithinImpl([&] { fn(); });
}
template <typename Fn>
auto _callWithin(const Fn& fn, std::false_type) -> decltype(fn()) {decltype(fn()) result{};callWithinImpl([&] { result = fn(); });return result;
}
  • 主函数 callWithin 调用辅助函数 _callWithin,并通过 std::is_same<decltype(fn()), void>() 传入类型标签(std::true_typestd::false_type)。
  • _callWithinvoid 返回的函数版本调用 callWithinImpl,直接执行,不返回结果。
  • _callWithin 对非 void 返回的函数版本,保存返回值 result,执行后返回。
    这是经典的 标签分派(tag dispatching) 技巧,用来根据类型选择不同函数版本,实现了对 void 返回类型的安全处理。
    简言之:
  • void 返回的函数调用后不返回值。
  • void 返回的函数调用后返回结果。

callWithinImpl 函数尝试执行以下操作:

bool callWithinImpl(const std::function<void()>& fn) {try {auto dbConnectionScope = database->openConnection();  // this might failfn();} catch (DBException& e) {return false;  // failure}return true;  // ok
}
  1. 尝试打开数据库连接auto dbConnectionScope = database->openConnection();
    这里打开数据库连接的操作可能会失败(例如抛出 DBException 异常)。
  2. 调用传入的函数 fn():执行用户传入的代码块。
  3. 异常处理:如果在打开连接或执行函数过程中抛出了 DBException,则捕获异常并返回 false,表示失败。
  4. 成功返回:没有异常时,返回 true,表示成功。
    这个函数负责在特定的上下文(数据库连接管理)中安全执行代码,并用返回值告知调用者执行是否成功。
template <typename Fn>
auto callWithin(const Fn& fn) -> boost::optional<decltype(fn())> {decltype(fn()) result{};bool ok = callWithinImpl([&]() { result = fn(); });if (ok)return result;  // wrapped within boost::optionalelsereturn boost::none;
}
bool callWithinImpl(const std::function<void()>& fn);

这个模板函数 callWithin 是对之前 callWithinImpl 的封装,用来处理可能失败的执行并返回可选值(boost::optional),具体流程如下:

  • 模板参数 Fn 是传入的可调用对象,decltype(fn()) 表示其返回值类型。
  • 创建一个变量 result 用于存储调用 fn() 的返回值。
  • 调用 callWithinImpl,并传入一个 lambda,该 lambda 调用 fn() 并将结果赋给 result
  • callWithinImpl 返回一个布尔值 ok 表示执行是否成功。
  • 如果成功 (ok == true),则返回 result,自动包装成 boost::optional<decltype(fn())>
  • 如果失败 (ok == false),则返回 boost::none,表示没有有效结果。
    这样设计可以优雅地捕获失败情况,并用 boost::optional 明确告诉调用者是否有返回值。调用者可以通过判断 optional 是否有值,来决定下一步处理。

这段代码是一个具体的示例,展示了如何使用 boost::optional 来处理函数调用可能失败的情况。以下是逐步解释:

1. 调用 callWithin 函数

auto result = callWithin([](){ return sum(1, 2); });
  • callWithin 是一个模板函数,它接受一个返回值的函数(这里是一个 lambda 函数 [](){ return sum(1, 2); })。该 lambda 调用 sum(1, 2) 来计算 1 和 2 的和(即 3)。
  • callWithin 函数会执行这个 lambda 并将结果包装成一个 boost::optional 类型。这样,结果就可以表示成功的返回值或者 boost::none,表示调用失败。

2. 检查 boost::optional 是否包含有效值

if (result) // might be boost::none
{double resultValue = *result; // dereference boost::optionalstd::cout << resultValue << std::endl;
}
  • if (result):这里使用 boost::optional 的隐式转换来检查 result 是否包含一个有效值。如果 result 包含一个值(即 boost::optional 不是 boost::none),那么 if 条件成立,表示调用成功。
  • *result:如果 result 有值,使用解引用操作符 * 来提取存储的值(这里是 sum(1, 2) 的返回值,即 3)。boost::optional 内部是一个包装类型,解引用操作符可以访问其中的实际值。
  • std::cout << resultValue << std::endl;:如果调用成功,打印结果 resultValue,在此例中是 3

总结:

  • boost::optional 是用来表示可能存在或不存在的值的类型。如果 callWithin 执行成功,result 会包含计算的结果;如果执行失败,result 会是 boost::none
  • 通过 if (result) 来判断 result 是否包含有效值,进而处理成功和失败的情况。
  • 解引用:如果结果有效,可以通过 *result 获取实际的返回值,并进行处理。

相关文章:

  • 安全-JAVA开发-第一天
  • 哪些IT运维工具支持自定义监控项?
  • 网络编程(计算机网络基础)
  • 力扣刷题Day 69:搜索二维矩阵(74)
  • LeetCode刷题 -- 542. 01矩阵 基于 DFS 更新优化的多源最短路径实现
  • WebFuture 系列产品 15.2.4 发布公告
  • 黑马Java面试笔记之 消息中间件篇(Kafka)
  • 【动手学机器学习】第三章模式识别与机器学习经典算法——k 近邻算法
  • 2025年AIR SCI1区TOP,多策略增强蜣螂算法MDBO+实际工程问题,深度解析+性能实测
  • 谷歌地图高清卫星地图2026中文版下载|谷歌地图3D卫星高清版 V7.3.6.9796 最新免费版下载 - 前端工具导航
  • 让AI弹琴作曲不再是梦:Python+深度学习玩转自动化音乐创作
  • 【Mysql】隐式转换造成索引失效
  • MATLAB 中调整超参数的系统性方法
  • CSS(2)
  • 便签软件哪个好用,最好用的免费便签软件介绍
  • 利用Python 进行自动化操作: Pyautogui 库
  • Python开发系统项目
  • 【软考】计算机系统构成及硬件基础知识
  • Java项目OOM排查
  • 平台化 LIMS 系统架构 跨行业协同与资源共享的实现路径
  • 个人网站备案填写/网络营销平台
  • 大型门户网站的建设外包在本公司制作好还是/seo怎么收费的
  • 国内真正的永久建站/怎么制作公司网页
  • 苏州营销型网站制作/厦门seo蜘蛛屯
  • 微信小程序做链接网站/浏阳廖主任打人案
  • 白狐网站建设/一般的电脑培训班要多少钱