1. 什么是Namespace
理解C++中的namespace(命名空间)对于前端开发者来说,可以类比为JavaScript中的模块化作用域或对象封装的概念。核心目的都是解决命名冲突和组织代码
JavaScript (ES6+) :类似模块化(import/export),不同模块的同名函数不会冲突。
// moduleA.js
export function render() { /* ... */ }
// moduleB.js
export function render() { /* ... */ }
// 使用时
import { render as renderA } from './moduleA.js';
import { render as renderB } from './moduleB.js';
renderA(); // 明确调用 模块A 的 render
renderB(); // 明确调用 模块B 的 render
C++ :多个库可能定义了相同名称的函数/类(例如 render())。namespace 将它们隔离在不同的作用域内。
// 库A
namespace LibraryA {
void render() { /* A的渲染逻辑 */ }
}
// 库B
namespace LibraryB {
void render() { /* B的渲染逻辑 */ }
}
int main() {
LibraryA::render(); // 明确调用 库A 的 render
LibraryB::render(); // 明确调用 库B 的 render
}
2. 为什么需要Namespace
// 假设没有namespace,不同库有同名类
class Scene { /* 图形库的Scene */ };
class Scene { /* 物理库的Scene */ }; // 编译错误!重复定义
// 有了namespace就不会冲突
namespace graphics {
class Scene { /* 图形库的Scene */ };
}
namespace physics {
class Scene { /* 物理库的Scene */ };
}
3. 使用Namespace的三种方式
方式1:完全限定名(类似对象属性访问)
threepp::Scene scene; // 相当于 threepp.Scene
threepp::Camera camera; // 相当于 threepp.Camera
physics::Scene physScene; // 相当于 physics.Scene
方式2:使用using声明(类似解构赋值)
using threepp::Scene; // 相当于 const { Scene } = threepp;
Scene scene; // 现在可以直接使用Scene
方式3:使用using namespace(类似全局导入)
using namespace threepp; // 相当于 import * as threepp from 'threepp'; 然后全局使用
// 现在可以直接使用threepp命名空间中的所有内容
Scene scene; // 不需要 threepp::Scene
Camera camera; // 不需要 threepp::Camera
4. 为什么多个文件中有相同的 namespace 命名空间而不会冲突
Namespace是逻辑分组,不是物理限制
4.1 JavaScript中的类似情况
在JavaScript中,你可以有多个文件向同一个模块导出不同的内容:
// file: BoxHelper.js
export class BoxHelper {
// BoxHelper的实现
}
// file: PlaneHelper.js
export class PlaneHelper {
// PlaneHelper的实现
}
// file: three-helpers.js - 统一导出
export { BoxHelper } from './BoxHelper.js';
export { PlaneHelper } from './PlaneHelper.js';
// 使用时
import { BoxHelper, PlaneHelper } from 'three-helpers';
4.2 C++中的Namespace工作方式
// BoxHelper.hpp文件
namespace threepp {
class BoxHelper { // 定义BoxHelper类
// ...
};
}
// PlaneHelper.hpp文件
namespace threepp {
class PlaneHelper { // 定义PlaneHelper类
// ...
};
}
// 最终效果相当于:
namespace threepp {
class BoxHelper { /* ... */ };
class PlaneHelper { /* ... */ };
// 所有其他threepp类...
}
4.3 关键理解点
- Namespace ≠ 文件
// 可以有100个文件都包含 namespace threepp
// 编译器会把它们合并成一个大的namespace
- 冲突的是类名,不是namespace名
namespace threepp {
class BoxHelper { }; // ✅ 正确
class PlaneHelper { }; // ✅ 正确
class BoxHelper { }; // ❌ 错误!重复定义BoxHelper
}
5. JavaScript对比总结
| C++ | JavaScript等价概念 |
|---|---|
| namespace threepp { ... } | import/export模块导出 |
| threepp::Scene | threepp.Scene |
| using threepp::Scene | const { Scene } = threepp |
| using namespace threepp | import * as threepp from 'threepp' + 全局暴露 |
6. 看一份threepp项目的源码
threepp 是将 JavaScript 3D 库 Three.js 移植到 C++ 的一个项目,可以用来
WASM相关的学习开发
// C++的#include相当于JavaScript的import,引入所需的头文件
#include "threepp/helpers/BoxHelper.hpp" // 盒子助手类,类似Three.js的BoxHelper
#include "threepp/helpers/PlaneHelper.hpp" // 平面助手类
#include "threepp/helpers/PolarGridHelper.hpp"// 极坐标网格助手类
// 重要概念:上面三个文件都定义了 namespace threepp,但它们不冲突!
// 原因:namespace是逻辑分组,不是物理限制
//
// 类比JavaScript:
// BoxHelper.hpp → export class BoxHelper {}
// PlaneHelper.hpp → export class PlaneHelper {}
// 最终合并成: → export { BoxHelper, PlaneHelper }
//
// C++编译器会自动合并所有相同名称的namespace,形成一个统一的命名空间:
// namespace threepp {
// class BoxHelper { }; // 来自BoxHelper.hpp
// class PlaneHelper { }; // 来自PlaneHelper.hpp
// class Scene { }; // 来自其他文件
// // ... 更多类
// }
#include "threepp/threepp.hpp"// 主要的threepp库,相当于 import * as THREE from 'three';
#include <cmath>// C++标准数学库,相当于JavaScript的Math
// using namespace相当于JavaScript的全局导入所有exports
// 类似于: import * as threepp from 'threepp'; 然后将所有内容暴露到全局作用域
// 好处:可以直接写 Scene::create() 而不用 threepp::Scene::create()
// 坏处:可能造成命名冲突,就像JavaScript中污染全局作用域一样
using namespace threepp;
// 如果不使用 using namespace,代码会是这样:
// auto scene = threepp::Scene::create(); // 需要前缀threepp::
// auto camera = threepp::PerspectiveCamera::create();
// threepp::Canvas canvas("Helpers");
//
// 使用 using namespace 后,可以直接写:
// auto scene = Scene::create(); // 直接使用,更简洁
// auto camera = PerspectiveCamera::create();
// Canvas canvas("Helpers");
int main() {
Canvas canvas("Helpers");
GLRenderer renderer(canvas.size());
auto scene = Scene::create();
auto camera = PerspectiveCamera::create(75, canvas.aspect(), 0.1f, 1000);
camera->position.z = 2;
camera->position.y = 1;
OrbitControls controls{*camera, canvas};
const auto arrow = ArrowHelper::create({0, 1, 0}, {0, 0, 0}, 0.5f, 0xff0000);
arrow->position.setX(0.5f);
scene->add(arrow);
const auto boxHelper = BoxHelper::create(*arrow);
scene->add(boxHelper);
const auto axes = AxesHelper::create(1);
axes->position.setX(-0.5f);
scene->add(axes);
const auto grid = GridHelper::create(5);
scene->add(grid);
Box3 box({-1, -1, -1}, {1, 1, 1});
const auto box3Helper = Box3Helper::create(box);
box3Helper->position.setY(1);
scene->add(box3Helper);
Plane plane(Vector3(0.5, 1, 0.5), 1);
const auto planeHelper = PlaneHelper::create(plane);
scene->add(planeHelper);
PolarGridHelper::Options polarOpts;
polarOpts.radius = 5;
polarOpts.color2 = Color::lightblue;
auto polar = PolarGridHelper::create(polarOpts);
scene->add(polar);
canvas.onWindowResize([&](WindowSize size) {
camera->aspect = size.aspect();
camera->updateProjectionMatrix();
renderer.setSize(size);
});
Clock clock;
canvas.animate([&]() {
const auto dt = clock.getDelta();
arrow->rotation.z += 0.5f * dt;
axes->rotation.y += 0.5f * dt;
float sineWave = 0.5f * std::sin(math::TWO_PI * 0.1f * clock.elapsedTime) + 1;
box.setFromCenterAndSize({0, 0, 0}, Vector3(1, 1, 1).multiplyScalar(sineWave));
boxHelper->update();
renderer.render(*scene, *camera);
});
return 0;
}