【问题标题】:Using A Namespace In Place Of A Class使用命名空间代替类
【发布时间】:2016-07-27 03:31:18
【问题描述】:

我正在设计一个接口来抽象管理 Direct3D、Direct2D、DXGI 和相关 Win32API 调用的任务。

将所有内容保存在命名空间中或重构以使用类?

WindowsApp.h

#pragma once
#include <Windows.h>

namespace WindowsApp
{
    bool Initialize(HINSTANCE instanceHandle);
}

WindowsApp.cpp

#include "WindowsApp.h"

namespace WindowsApp
{
    namespace
    {
        HWND ghMainWnd = 0;
    }

    LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        switch (msg)
        {
        case WM_DESTROY:
        {
            PostQuitMessage(0);
            return 0;
        }
        break;

        default:
        {
            return DefWindowProc(hWnd, msg, wParam, lParam);
        }
        }
    }

    bool Initialize(HINSTANCE instanceHandle)
    {
        WNDCLASS wc;
        wc.style = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc = WndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = instanceHandle;
        wc.hIcon = LoadIcon(0, IDI_APPLICATION);
        wc.hCursor = LoadCursor(0, IDC_ARROW);
        wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
        wc.lpszMenuName = 0;
        wc.lpszClassName = L"BasicWndClass";

        if (!RegisterClass(&wc))
        {
            MessageBox(0, L"RegisterClass FAILED", 0, 0);
            return false;
        }

        ghMainWnd = CreateWindow(
            L"BasicWndClass",
            L"Win32Basic",
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            0,
            0,
            instanceHandle,
            0);

        if (ghMainWnd == 0)
        {
            MessageBox(0, L"CreateWindow FAILED", 0, 0);
        }

        ShowWindow(ghMainWnd, 1);
        UpdateWindow(ghMainWnd);

        return true;
    }
}

Main.cpp

#include "WindowsApp.h"

int Run()
{
    MSG msg = { 0 };

    BOOL bRet = 1;
    while ((bRet = GetMessage(&msg, 0, 0, 0)) != 0)
    {
        if (bRet == -1)
        {
            MessageBox(0, L"GetMessage FAILED", L"Error", MB_OK);
            break;
        }
        else
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    // Deinitialize Here
    return (int)msg.wParam;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, int nShowCmd)
{
    if (!WindowsApp::Initialize(hInstance)) { return 0; }
    return Run();
}

使用命名空间允许我在嵌套的未命名命名空间内隐藏实现细节。 一个类不会让我隐藏东西,但我可以让它们在私有部分中无法访问,我想这已经足够了。

使用类存在用户尝试实例化多个对象的风险,这会导致应用程序因两次初始化 DirectX 或其他原因而崩溃。 命名空间避免了这个问题,但是它们会降低性能,我必须在每个函数调用期间检查 Initialized 变量。我真的不喜欢这个。

最后,使用类要求用户在整个应用程序中需要底层方法的地方传递实例化对象。这真是令人失望,因为每当我在一个文件中时,命名空间方法让我可以访问,该文件有一个 #include 到命名空间头文件。我真的很喜欢这个。

命名空间方法似乎是最好的方法,但在嵌套的未命名命名空间内处理变量的方式中,有些东西并不适合我。 这样可以吗?我的直觉告诉我不行!不!不!

所以我想我的问题是:这是命名空间的合适用例吗?

澄清: 我在 WindowsApp.cpp 中定义了未命名的命名空间以及函数定义——所有函数的前向声明都在 WindowsApp.h 中——通过调用这些函数来操纵未命名命名空间内的变量。这是对命名空间的不当使用还是应该以不同的方式完成?只需将头文件包含在任何其他 .cpp 中,您就可以访问函数,进而访问基础数据。这是非常吸引人的。我的直觉告诉我,这样的结构会导致某种性能损失。

【问题讨论】:

  • 听起来你在寻找单例模式
  • 乍一看确实如此。这种命名空间模式听起来像是更好的方法,我可以大声声明嵌套未命名命名空间内的变量,然后通过父命名命名空间内定义的函数访问它们。当您只需 #include 包含对其中命名空间函数的所有前向声明的文件时,不必为了访问它的成员函数而在应用程序周围传递一个实例化的类对象似乎是不必要的。
  • 你不必到处传递一个实例,单个实例可以静态获取WindowsAppClass::getInstance()

标签: c++ class namespaces


【解决方案1】:

[编辑:删除关于未命名命名空间的内容开始是 TU 独有的,现在问题中的代码已得到澄清。]

在 C++ 中,我们倾向于将 class 视为一些保持不变量的数据的包装器(与 struct 相对,后者倾向于用于一堆没有不变量的数据)。构造函数建立不变量,析构函数将其拆除,成员函数小心维护它。在这里,如果我理解正确,您的不变量似乎是 Initialized() 必须在使用任何其他 API 函数之前调用。

还有另一种选择,即使用所谓的“魔术静力学”,也称为“Meyers 单例”。执行以下操作:

// In WindowsApp.cpp

namespace {

class WindowsAppImpl {
public:
    WindowsAppImpl()
    {
        // Do initialization stuff here
    }

    ~WindowsAppImpl()
    { 
        // Do teardown stuff if necessary
    }

    // Example function
    int getMagicNumber()
    {
        return 3;
    }
};

WindowsAppImpl& GetInstance() {
    static WindowsAppImpl instance{};
    return instance;
}

} // end private namespace

// Public function declared in WindowApp.h
int GetMagicNumber() {
    // Get the singleton instance
    WindowsAppImpl& instance = GetInstance();

    // Call member function
    return instance.getMagicNumber();
}

这种方法添加了一个函数,该函数返回对单例WindowsAppImpl 实例的引用。编译器保证这个实例只构造一次,第一次调用GetInstance()。 (它还将在main() 完成后运行WindowsAppImpl 的析构函数,这在这种情况下可能并不重要,但在某些情况下可能很有用。)这种方法的优点是在GetMagicNumber() 内部你可以确定初始化例程已运行,无需用户传递某种自己的 WindowsAppContext 实例。

【讨论】:

  • 我在 WindowsApp.cpp 中定义了未命名的命名空间以及函数定义 - 所有函数的前向声明都在 WindowsApp.h 中 - 通过使用调用这些函数来操纵未命名中的变量命名空间。这是对命名空间的不当使用还是应该以不同的方式完成?只需将头文件包含在任何其他 .cpp 中,您就可以访问函数,进而访问基础数据。这是非常吸引人的。我的直觉告诉我,这样的结构会导致某种性能损失。
  • in C++ we tend to think of a class as being a wrapper for some data which maintains an invariant (as opposed to a struct, which tends to be used for a bunch of data with no invariant). 您介意详细说明一下吗?我会选择退出 *we* tend to
  • @dxiv 语言中classstruct 关键字的含义几乎没有区别(您可以将class 的所有用法替换为普通C++ 程序中的struct 和它仍然可以工作),因此不变与非不变的区别更像是一种有用的约定,而不是语言严格要求的东西。就像每个约定一样,尽管不是每个人都遵守它,而且很容易找到不这样做的代码。这就是为什么我说“我们倾向于”...看到 struct 使用是一个强烈的迹象,没有不变,但(可悲)不是保证。
  • @KKlouzal 啊,好的,我明白了。在这种情况下,我仍然建议将您的初始化内容包装在一个类中(WindowsApp.h 中的私有),并使用“魔术静态”方法,这样您就可以获得有保证的初始化顺序。全局变量很糟糕,尤其是在 C++ 中,因为编译器在 main() 之前初始化它们的顺序基本上是随机的。使用神奇的静态方法至少可以解决后一个问题。
  • 在命名空间中声明变量会给它们命名空间范围吗?这还像全球一样糟糕吗?初始化顺序是随机的是一件非常糟糕的事情。我已经用逐字示例代码更新了 OP。但是,如果命名空间变量被认为是全局变量(即使在 .cpp 文件中定义?)并且具有随机的初始化顺序,那么它就是一个交易破坏者。 :( :( :(
猜你喜欢
  • 2017-08-06
  • 1970-01-01
  • 1970-01-01
  • 2020-05-04
  • 2014-09-24
  • 1970-01-01
  • 2012-09-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多