封装:公共成员与公共方法

时间:2017-06-21 00:43:18

标签: c++ c++14 encapsulation

假设我有一个名为PointerSet<T>的类。它基本上是一个vector,通过使用set就像std::lower_bound一样(但它的工作方式并不重要,所以我不会进一步详细说明)。

template<typename T>
class PointerSet
{
    ...

public:
    void insert(T&);
    void erase(T&);
    void erase(const std::vector<T>&);
};

将拥有一个&#34;功能隧道&#34;表现受到打击? (我不知道技术术语,但这是我在脑海里所说的。它是一个函数,它的功能与完全相同。)请参阅版本1

版本1:创建成员方法

...

template<typename T>
class Node
{
    PointerSet<T> Links;
public:
    void insertLink(T& p){ Links.insert(p); }
    void eraseLink(T& p){ Links.erase(p); }
    void eraseLink(const std::vector<T>& p){ Links.erase(p); }
};

class bar;
class foo : public Node<bar> { };    // now foo can insert links to bars.

...

版本2:只需使用公开会员

...

class bar;
struct foo
{
    PointerSet<bar> Links;    // Use Links directly
};

其中哪一种是最好的方法?我正在考虑性能易于调试。

3 个答案:

答案 0 :(得分:3)

  

将拥有一个&#34;功能隧道&#34;打击表演?

很可能不是。特别是因为你在这里处理模板,所以定义将是可见的,并且编译器可以很容易地内联。请看一下这个链接:https://godbolt.org/g/cWR7N3

我所做的是编译这两个代码段。首先,从Node类调用函数。

#include <vector>

// these functions are not defined, so that the compiler
// cannot inline them or optimize them out
void insert_impl(void const*);
void erase_impl(void const*);
void erase_impl_vec(void const*);

template<typename T>
class PointerSet
{
public:
    void insert(T& v) { insert_impl(&v); }
    void erase(T& v) { erase_impl(&v); }
    void erase(const std::vector<T>& v) {
        erase_impl_vec(&v);
    }
};

template<typename T>
class Node
{
    PointerSet<T> Links;
public:
    void insertLink(T& p){ Links.insert(p); }
    void eraseLink(T& p){ Links.erase(p); }
    void eraseLink(const std::vector<T>& p){ Links.erase(p); }
};

int main()
{
    Node<int> n;

    int x;
    n.insertLink(x);
    n.eraseLink(x);

    std::vector<int> v;
    n.eraseLink(v);
}

然后直接从PointerSet类调用它们。

#include <vector>

// these functions are not defined, so that the compiler
// cannot inline them or optimize them out
void insert_impl(void const*);
void erase_impl(void const*);
void erase_impl_vec(void const*);

template<typename T>
class PointerSet
{
public:
    void insert(T& v) { insert_impl(&v); }
    void erase(T& v) { erase_impl(&v); }
    void erase(const std::vector<T>& v) {
        erase_impl_vec(&v);
    }
};

int main()
{
    PointerSet<int> n;

    int x;
    n.insert(x);
    n.erase(x);

    std::vector<int> v;
    n.erase(v);
}

正如您在链接(https://godbolt.org/g/cWR7N3)中看到的那样,编译器为每个链接输出相同的汇编。

main:                                   # @main
        push    rbx
        sub     rsp, 48
        lea     rbx, [rsp + 12]
        mov     rdi, rbx
        call    insert_impl(void const*)
        mov     rdi, rbx
        call    erase_impl(void const*)
        xorps   xmm0, xmm0
        movaps  xmmword ptr [rsp + 16], xmm0
        mov     qword ptr [rsp + 32], 0
        lea     rdi, [rsp + 16]
        call    erase_impl_vec(void const*)
        mov     rdi, qword ptr [rsp + 16]
        test    rdi, rdi
        je      .LBB0_3
        call    operator delete(void*)
.LBB0_3:
        xor     eax, eax
        add     rsp, 48
        pop     rbx
        ret
        mov     rbx, rax
        mov     rdi, qword ptr [rsp + 16]
        test    rdi, rdi
        je      .LBB0_6
        call    operator delete(void*)
.LBB0_6:
        mov     rdi, rbx
        call    _Unwind_Resume
GCC_except_table0:
        .byte   255                     # @LPStart Encoding = omit
        .byte   3                       # @TType Encoding = udata4
        .byte   41                      # @TType base offset
        .byte   3                       # Call site Encoding = udata4
        .byte   39                      # Call site table length
        .long   .Lfunc_begin0-.Lfunc_begin0 # >> Call Site 1 <<
        .long   .Ltmp0-.Lfunc_begin0    #   Call between .Lfunc_begin0 and .Ltmp0
        .long   0                       #     has no landing pad
        .byte   0                       #   On action: cleanup
        .long   .Ltmp0-.Lfunc_begin0    # >> Call Site 2 <<
        .long   .Ltmp1-.Ltmp0           #   Call between .Ltmp0 and .Ltmp1
        .long   .Ltmp2-.Lfunc_begin0    #     jumps to .Ltmp2
        .byte   0                       #   On action: cleanup
        .long   .Ltmp1-.Lfunc_begin0    # >> Call Site 3 <<
        .long   .Lfunc_end0-.Ltmp1      #   Call between .Ltmp1 and .Lfunc_end0
        .long   0                       #     has no landing pad
        .byte   0                       #   On action: cleanup

答案 1 :(得分:1)

除非您想限制可以对该数据成员执行的操作,否则我建议您使用该公共成员。如果您所做的只是使用自己的方法名称包装方法,那么将它们包含在那里是没有意义的。

答案 2 :(得分:0)

  • 一般情况下,如果不遵守IS-A关系,则应避免公开推导,因为它会导致更高的组合耦合。
  • 您还应该避免使用公共成员,因为它可能会使应用程序在将来更难维护。
  • 如果您可以在第二个示例中直接使用PointerSet,那么在版本1中,您也可以直接从PointerSet派生。因此比较是不公平的。

在实践中,可接受的妥协在很大程度上取决于每个班级的实际目的。如果他们没有给予任何好处(减少编译依赖性,访问/可见性控制,额外功能,代码重用机会......)