前端学C++可太简单了:namespace 命名空间

201 阅读4分钟

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::Scenethreepp.Scene
using threepp::Sceneconst { Scene } = threepp
using namespace threeppimport * as threepp from 'threepp' + 全局暴露

6. 看一份threepp项目的源码

threepp 是将 JavaScript 3D 库 Three.js 移植到 C++ 的一个项目,可以用来 WASM 相关的学习开发

image.png

// 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;
}