在 C++ 中,实现 const 与非 const 重载方法的代码复用有多种技术。现介绍几种常见的方法:
1. 基本的 const 和非 const 重载
问题示例 - 代码重复
class MyVector {
private:
int* data;
size_t size;
public:
// 非 const 版本
int& at(size_t index) {
// 边界检查、日志等公共逻辑
if (index >= size) throw std::out_of_range("Index out of range");
return data[index];
}
// const 版本 - 大量重复代码!
const int& at(size_t index) const {
// 重复的边界检查、日志等公共逻辑
if (index >= size) throw std::out_of_range("Index out of range");
return data[index];
}
};
2. 方法1:使用 const_cast(不推荐)
class MyVector {
private:
int* data;
size_t size;
public:
// const 版本作为主要实现
const int& at(size_t index) const {
if (index >= size) throw std::out_of_range("Index out of range");
return data[index];
}
// 非 const 版本调用 const 版本
int& at(size_t index) {
// 使用 const_cast 移除 const
return const_cast<int&>(static_cast<const MyVector*>(this)->at(index));
}
};
缺点:const_cast 可能不安全,违反了 const 正确性。
3. 方法2:使用辅助私有方法(推荐)
class MyVector {
private:
int* data;
size_t size;
// 私有辅助方法,包含公共逻辑
void checkIndex(size_t index) const {
if (index >= size)
throw std::out_of_range("Index out of range");
}
public:
// const 版本
const int& at(size_t index) const {
checkIndex(index); // 复用公共逻辑
return data[index];
}
// 非 const 版本
int& at(size_t index) {
checkIndex(index); // 复用公共逻辑
return data[index];
}
};
4. 方法3:提取公共逻辑到单独方法
class MyVector {
private:
int* data;
size_t size;
// 公共逻辑提取
size_t validateIndex(size_t index) const {
if (index >= size)
throw std::out_of_range("Index out of range");
return index;
}
public:
const int& at(size_t index) const {
return data[validateIndex(index)];
}
int& at(size_t index) {
return data[validateIndex(index)];
}
};
5. 方法4:使用模板和 SFINAE(高级技巧)
#include <type_traits>
class MyVector {
private:
int* data;
size_t size;
// 公共验证逻辑
void checkIndex(size_t index) const {
if (index >= size)
throw std::out_of_range("Index out of range");
}
public:
// 统一的接口
template<typename Self>
auto&& at(this Self&& self, size_t index) {
self.checkIndex(index);
return self.data[index];
}
};
注意:这使用了 C++23 的显式对象参数,需要现代编译器支持。
6. 方法5:对于复杂场景使用 CRTP
// CRTP 基类模板
template<typename Derived>
class VectorBase {
protected:
void checkIndex(size_t index, size_t size) const {
if (index >= size)
throw std::out_of_range("Index out of range");
}
public:
// const 版本
auto at(size_t index) const -> const typename Derived::value_type& {
const auto& self = static_cast<const Derived&>(*this);
checkIndex(index, self.size());
return self.data()[index];
}
// 非 const 版本
auto at(size_t index) -> typename Derived::value_type& {
auto& self = static_cast<Derived&>(*this);
checkIndex(index, self.size());
return self.data()[index];
}
};
// 具体实现
class MyVector : public VectorBase<MyVector> {
private:
int* data_;
size_t size_;
public:
using value_type = int;
MyVector(size_t size) : size_(size), data_(new int[size]) {}
~MyVector() { delete[] data_; }
size_t size() const { return size_; }
int* data() { return data_; }
const int* data() const { return data_; }
// 自动获得 at() 方法
};
7. 方法6:使用宏(不推荐但实用)
#define DEFINE_AT_METHODS() \
const int& at(size_t index) const { \
if (index >= size) throw std::out_of_range("Index out of range"); \
return data[index]; \
} \
int& at(size_t index) { \
return const_cast<int&>(static_cast<const MyVector*>(this)->at(index)); \
}
class MyVector {
private:
int* data;
size_t size;
public:
DEFINE_AT_METHODS()
// 其他方法...
};
8. 实际完整示例
#include <iostream>
#include <stdexcept>
#include <vector>
class StringArray {
private:
std::vector<std::string> data;
// 公共验证逻辑
void validateIndex(size_t index) const {
if (index >= data.size()) {
throw std::out_of_range("Index " + std::to_string(index) +
" out of range for size " +
std::to_string(data.size()));
}
}
// 公共业务逻辑
void logAccess(size_t index, const std::string& operation) const {
std::cout << operation << " at index " << index << std::endl;
}
public:
StringArray(std::initializer_list<std::string> init) : data(init) {}
// const 版本
const std::string& at(size_t index) const {
validateIndex(index);
logAccess(index, "Read access");
return data[index];
}
// 非 const 版本
std::string& at(size_t index) {
validateIndex(index);
logAccess(index, "Write access");
return data[index];
}
// 同样处理 operator[]
const std::string& operator[](size_t index) const {
return at(index); // 复用 at 的逻辑
}
std::string& operator[](size_t index) {
return at(index); // 复用 at 的逻辑
}
size_t size() const { return data.size(); }
};
int main() {
StringArray arr{"Hello", "World", "C++"};
// 使用 const 版本
const StringArray& constArr = arr;
std::cout << constArr.at(0) << std::endl; // 调用 const 版本
// 使用非 const 版本
arr.at(1) = "Programming"; // 调用非 const 版本
std::cout << arr[1] << std::endl; // 调用非 const 版本
return 0;
}
9. 最佳实践总结
推荐做法:
- 提取公共验证逻辑到私有方法
- 提取公共业务逻辑到私有方法
- 保持两个版本方法简洁,只包含必要的差异
- 优先选择方法2和方法3,它们最安全且易维护
避免的做法:
- 大量代码重复在两个版本中
- 过度使用 const_cast,可能破坏 const 正确性
- 复杂的模板技巧,除非确实需要
现代 C++ 趋势:
- C++23 的显式对象参数可以简化这种模式
- 概念(Concepts)可以提供更好的类型约束
- 始终优先考虑代码的可读性和维护性
选择哪种方法取决于具体的代码复杂度、团队约定和性能要求。对于大多数情况,方法2(提取公共逻辑)是最实用和安全的選擇。
1413

被折叠的 条评论
为什么被折叠?



