跳转至

关于函数的潜在优化

1.返回值优化(RVO、NRVO)

#include <iostream>
#include <memory>
#include <string>
#include <vector>

// 用于追踪对象生命周期的基类
class Tracer {
 public:
  Tracer(const std::string& name) : name_(name) {
    std::cout << "[" << name_ << "] Default constructor @ " << this << "\n";
  }

  Tracer(const Tracer& other) : name_(other.name_ + "_copy") {
    std::cout << "[" << name_ << "] Copy constructor from " << &other << " to "
              << this << "\n";
  }

  Tracer(Tracer&& other) noexcept : name_(std::move(other.name_) + "_moved") {
    std::cout << "[" << name_ << "] Move constructor from " << &other << " to "
              << this << "\n";
  }

  ~Tracer() { std::cout << "[" << name_ << "] Destructor @ " << this << "\n"; }

  Tracer& operator=(const Tracer& other) {
    if (this != &other) {
      name_ = other.name_ + "_copy_assigned";
      std::cout << "[" << name_ << "] Copy assignment from " << &other << " to "
                << this << "\n";
    }
    return *this;
  }

  Tracer& operator=(Tracer&& other) noexcept {
    if (this != &other) {
      name_ = std::move(other.name_) + "_move_assigned";
      std::cout << "[" << name_ << "] Move assignment from " << &other << " to "
                << this << "\n";
    }
    return *this;
  }

  const std::string& name() const { return name_; }

 private:
  std::string name_;
};

// 不可拷贝也不可移动的类(C++17 之前无法返回这类对象)
class NonCopyableNonMovable {
 public:
  NonCopyableNonMovable(const std::string& id) : id_(id) {
    std::cout << "[NonCopyableNonMovable] Constructor: " << id_ << " @ " << this
              << "\n";
  }

  ~NonCopyableNonMovable() {
    std::cout << "[NonCopyableNonMovable] Destructor: " << id_ << " @ " << this
              << "\n";
  }

  // 禁止拷贝和移动
  NonCopyableNonMovable(const NonCopyableNonMovable&) = delete;
  NonCopyableNonMovable& operator=(const NonCopyableNonMovable&) = delete;
  NonCopyableNonMovable(NonCopyableNonMovable&&) = delete;
  NonCopyableNonMovable& operator=(NonCopyableNonMovable&&) = delete;

 private:
  std::string id_;
};

// 原始代码中的结构
struct LargeClass {
  int i;
  char c;
  double d;
};

// 基本 RVO:返回临时对象(C++17 起强制拷贝消除)
auto rvoExample(int i, char c, double d) {
  return LargeClass{i, c,
                    d};  // 不要在这里使用 std::move,会阻止编译器进行完全优化
}

// NRVO:返回命名对象(不是强制的,取决于编译器)
Tracer nrvoExample(const std::string& name) {
  Tracer obj(name);  // 命名局部对象
  return obj;        // 现代编译器通常会优化掉拷贝/移动
}

// 混合返回路径(可能阻止 NRVO)
Tracer conditionalReturn(bool flag) {
  if (flag) {
    return Tracer("temporary");  // 临时对象 - 更容易优化
  } else {
    Tracer named("named");  // 命名对象
    return named;           // 可能优化,取决于编译器
  }
}

// 多返回点(现代编译器通常能处理)
Tracer multipleReturns(int value) {
  if (value < 0) return Tracer("negative");
  if (value == 0) return Tracer("zero");
  return Tracer("positive");  // 所有路径返回相同类型
}

// C++17 强制拷贝消除示例:返回不可拷贝/不可移动对象
NonCopyableNonMovable createNonCopyableNonMovable() {
  return NonCopyableNonMovable("uncopyable_object");  // C++17 前无法编译
}

// 无法应用 RVO 的情况:返回参数
Tracer returnParameter(Tracer param) {
  // 通常无法对参数应用 RVO,因为 param 已经是现有对象
  return param;  // 会调用移动构造函数(如果有)或拷贝构造函数
}

// 无法应用 RVO 的情况:返回静态对象
Tracer& getStaticTracer() {
  static Tracer instance("static_instance");
  return instance;  // 返回引用,不涉及 RVO
}

// 模板函数中的 RVO
template <typename T>
T createObject() {
  return T();  // 模板中的 RVO,取决于实例化类型
}

// 返回 std::vector(大型对象)的示例
std::vector<int> createLargeVector() {
  std::vector<int> vec(1000, 42);  // 创建大向量
  return vec;                      // NRVO 适用,或 C++11 后会使用移动语义
}

// 有副作用的计数器(注意:依赖拷贝副作用是危险的)
class Counter {
 public:
  Counter() { ++instance_count_; }
  Counter(const Counter&) { ++instance_count_; }
  ~Counter() { --instance_count_; }

  static int instance_count() { return instance_count_; }

 private:
  static int instance_count_;
};
int Counter::instance_count_ = 0;

Counter createCounter() {
  return Counter();  // RVO 会影响 instance_count_ 的值
}

int main() {
  std::cout << "===== 基本 RVO 示例 =====\n";
  LargeClass lc_obj = rvoExample(10, 'c', 3.14);

  std::cout << "\n===== NRVO 示例 =====\n";
  Tracer nrvo_obj = nrvoExample("nrvo_test");

  std::cout << "\n===== 条件返回示例 =====\n";
  Tracer cond_obj1 = conditionalReturn(true);
  Tracer cond_obj2 = conditionalReturn(false);

  std::cout << "\n===== 多返回点示例 =====\n";
  Tracer multi_obj1 = multipleReturns(-1);
  Tracer multi_obj2 = multipleReturns(0);
  Tracer multi_obj3 = multipleReturns(1);

  std::cout << "\n===== C++17 强制拷贝消除示例 =====\n";
  auto non_copyable = createNonCopyableNonMovable();  // C++17 前无法编译

  std::cout << "\n===== 无法应用 RVO 的情况:返回参数 =====\n";
  Tracer param_obj = returnParameter(Tracer("parameter"));

  std::cout << "\n===== 无法应用 RVO 的情况:返回静态对象 =====\n";
  Tracer& static_ref = getStaticTracer();
  std::cout << "Static object name: " << static_ref.name() << "\n";

  std::cout << "\n===== 模板函数中的 RVO =====\n";
  auto template_obj = createObject<Tracer>();

  std::cout << "\n===== 大型对象返回示例 =====\n";
  auto large_vec = createLargeVector();
  std::cout << "Vector size: " << large_vec.size() << "\n";

  std::cout << "\n===== 依赖拷贝副作用的危险示例 =====\n";
  {
    Counter c1 = createCounter();
    std::cout << "Counter instances after RVO: " << Counter::instance_count()
              << "\n";
  }

  // 演示强制拷贝(禁用 RVO)的情况
  std::cout << "\n===== 禁用 RVO 的情况(如果编译时使用 "
               "-fno-elide-constructors) =====\n";
  Tracer forced_copy = Tracer(
      "forced_copy");  // 如果使用 -fno-elide-constructors,这会产生额外拷贝

  return 0;
}

2.纯函数(pure function)

  • 没有副作用(side-effects) - 不会修改任何外部状态或全局变量
  • 返回值仅依赖于输入参数 - 不依赖任何外部状态
  • 对相同的输入,总是返回相同的结果 - 完全确定性行为
  • 本质上遵循数学函数的概念

纯函数的特性使编译器能够进行多种高级优化,但是编译器识别纯函数有局限性

  • 跨模块编译问题 - 当函数定义在不同模块或库中,编译器无法确定其是否为纯函数
  • 缺乏标准化标注 - 没有跨编译器统一的方式来声明函数为纯函数
#ifdef __GNUC__
#define pure_function __attribute__((const))
#else
#define pure_function
#endif

double Func1(double) pure_function;
double Func2(double x) { return Func1(x) * Func1(x) + 1.; }
  • GNU编译器: 会识别const属性,只调用一次Func1
  • 其他编译器: 由于没有标准属性,会调用两次Func1

不同编译器的纯函数属性

编译器 属性/关键字 说明
GCC/Clang __attribute__((const)) 严格纯函数,不访问内存
GCC/Clang __attribute__((pure)) 函数不修改内存,但可能读取全局内存
MSVC __declspec(noalias) 限制指针别名,部分相关
C++11/14/17 constexpr 有限的编译时纯函数支持
C++20 [[nodiscard]] 虽不直接标记纯函数,但可配合使用