【问题标题】:Is there a better way than friend classes here?这里有比朋友班更好的方法吗?
【发布时间】:2013-02-04 22:29:39
【问题描述】:

使用 C++ for Windows 进行编程,尽管这种情况可能在任何地方出现。这是我的问题的简化版本,以使问题易于管理,因此不要太关注细节:)

我有一个类 class Window,其中包含一个 windows HWND 数据项。我想完全封装HWND,以便该类的用户必须通过该类才能对该窗口执行任何操作,因此它存储在一个私有成员变量中。我不想为其提供任何公共“getter”,因为这会破坏封装,允许用户绕过我的课程。

现在我想创建一个类class Direct3d11 来封装一些directx api。为了创建这个类的一个实例,它需要一个窗口的 HWND,所以我在它的构造函数中传递了一个 Window 对象。

class Direct3D11
{
public:
    Direct3D11(const Window& window);
};

在构造函数内部,它可以访问窗口对象,但是它需要包含在其中的 HWND 才能物理创建 Direct3D11 类将管理的窗口对象,但它无法获取该信息.

我可以添加一个私有的getter函数来获取HWND到Window类,然后让Direct3D11类成为Window的友元类,这样它就可以调用该函数了。

然而,这似乎不是很优雅,尤其是因为 Window 类根本不需要了解有关 Direct3D11 类的任何信息。

我是否错过了实现这一目标的更好方法?朋友类没有吸引力,拥有公共 getter 函数也没有太大吸引力。

【问题讨论】:

  • 在我看来确实是friend 的用例。
  • 您基本上有两个选择,要么让所有人都可以使用 getter(即使其成为public),要么使其仅对某些其他人可用(即使用friend)。这些是合乎逻辑的可能性。关于我能想到的唯一其他不同的方法是让Direct3D11 继承自Window;这是否有意义取决于您... ;)
  • @OliCharlesworth:恕我直言,这令人讨厌。我仍然喜欢将继承视为 IS-A 关系的一种形式(尽管它不能真正准确地对其进行建模,但那是另一回事了)。在这里,使Direct3D11 继承自Window 将建立无意义的关系。我想这是friend 的自然用例。
  • 有很多 Windows API 需要 HWND,Direct3D 只是一个例子。我认为你应该只为 HWND 提供一个吸气剂,否则你会限制用户太多。
  • 您可以创建一个中间类,它是 Windows 的朋友,它通过私有访问器访问 HWND,然后将该类与 Direct3D11 建立朋友关系。但是,在我看来,这似乎毫无意义。

标签: c++


【解决方案1】:

您可以在Window 中创建Direct3D11 类,因为Windows 拥有HWND

类似的东西:

class Window
{
    HWND hwnd;
    Direct3D11 d;
public:
    Window() : d(hwnd) {}
    Direct3D11& getDirect3D()
    {
       return d;
    }
}

【讨论】:

  • 并不是每个窗口都包含一个 Direct3D 对象。 Direct3D 通常只为应用程序中的一个窗口创建。
  • 或者,我想,提供一个按需创建 Direct3D11 的创建功能,例如Direct3D11* createDirect3D() { return new Direct3D11(hwnd); }
  • 我不确定我想这样做。它给了我一个想法。我可以创建某种工厂类,该类知道如何创建依赖于 hwnds 的各种对象并使 that 成为 Window 的朋友,从而将我的 Direct3D 类的任何知识都保留在 Window 之外。需要更多思考...
【解决方案2】:

在您的情况下,我建议为 HWND 提供一个吸气剂,因为您可能会更频繁地需要它。提供 getter 并不意味着您承担 Window 类的责任,它仍然负责窗口的生命周期。你只是让它更有用,更容易在用例中划分你的代码。

也就是说,您可以尝试以下更通用的方法:

class Window;

class Direct3D {
public:
    void apply(Window &window, HWND hWnd);
};

class Window {
public:
    void accept(Direct3D &direct3d) {
        direct3d.apply(*this, this->m_hWnd);
    }
};

【讨论】:

    【解决方案3】:

    您可能在 Window 上有一个名为 Execute 的函数。它将接收一个带有 HWND 占位符的 std::function 作为参数。然后,Window 将调用该函数,并将 HWND 作为其唯一参数。

    这需要 c++11,但代码类似于:

    #include <functional>
    #include <iostream>
    
    struct Foo {
        explicit Foo(int num) : num_(num) {}
        template<typename T>
        void execute(std::function<T> f) const { f(num_); }
        private:
        int num_;
    };
    
    struct Bar{
        void print_nums(int i,int j)
        {
            std::cout << "i:" << i << ", " << "j:" << j << std::endl;
        }
    };
    
    int main()
    {
        Foo o(42);
        Bar b;
    
        //the function we want to execute requires an int
        //that Foo knows about
        typedef void myFunction(int);
    
        // store the result of a call to std::bind
        std::function<myFunction> display_1337_first = std::bind(&Bar::print_nums, b,1337, std::placeholders::_1);
        std::function<myFunction> display_1337_last = std::bind(&Bar::print_nums, b, std::placeholders::_1, 1337);
        o.execute<myFunction>(display_1337_first);
        o.execute<myFunction>(display_1337_last);
        return 0; 
    }
    
    //output:
    //i:1337, j:42
    //i:42, j:1337
    

    【讨论】:

    • 因为它允许任何人从窗口获取 HWND,我看不出它比简单地提供一个 getter 函数更好。它们都打破了封装,但这种方式使用起来要麻烦得多。
    • 我认为它更干净。当您想对句柄做某事时,您会调用执行。当然,您可以将值复制回来,但为什么要复制呢?我认为我能想到的最接近的类比是这个例子是一个“Put”er - 当需要值时,有问题的实例会填写所需的信息并继续函数调用。
    • 你真的认为win.execute([&amp;d3d](HWND hwnd){d3d = createDirect3D(hwnd);});d3d = createDirect3D(win.getHwnd()); 干净吗?无论如何,它不会回答问题,因为它破坏了封装。
    • 如果出现以下情况,它如何破坏封装:(1) 类在使用之前仍然可以控制对值的访问;(2) 它是一个 const 方法?
    • 它破坏了封装,因为它允许用户将 HWND 用于任何目的,OP 明确表示他不想允许。
    【解决方案4】:

    如果你愿意使用friend关键字,你可以确保window不知道需要hwnd的类。只需制作为您处理操作的类(该窗口和 DirectX 继承自)。这使您可以解决 DirectX 的问题,并在下次出现时解决。

    侧面咆哮: 朋友不是四个字母的词。如果使用得当,Friend 实际上是为 C++ 的访问控制(public、friend (when in protected)、protected、friend (when in private)、private)添加层次的好方法。

    #include <iostream>
    
    class HwndOwner;
    class HwndWanter
    {
    protected:
        HwndWanter(){}
        int getHwndFromOwner(HwndOwner & owner);
    };
    
    class HwndOwner
    {
    protected:
        HwndOwner() : hwnd(42){}
    
    private:
        friend class HwndWanter;
        int getHwnd()
        {
            return hwnd;
        }
    
        int hwnd;
    };
    
    class Window : public HwndOwner
    {
        //This is not the class you are looking for...
    };
    
    
    class Direct3D : private HwndWanter
    {
    public:
        Direct3D(HwndOwner & owner)
            : HwndWanter()
        {
    
            std::cout << getHwndFromOwner(owner) << std::endl;
        }
    };
    
    int HwndWanter::getHwndFromOwner(HwndOwner & owner)
    {
        return owner.getHwnd();
    }
    
    int main()
    {
        Window window;
        Direct3D hwndWanter(window);
    }
    

    输出:

    42
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-10-12
      • 2015-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-24
      相关资源
      最近更新 更多