假设我有一个名为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
};
其中哪一种是最好的方法?我正在考虑性能和易于调试。
答案 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)
PointerSet
,那么在版本1中,您也可以直接从PointerSet
派生。因此比较是不公平的。在实践中,可接受的妥协在很大程度上取决于每个班级的实际目的。如果他们没有给予任何好处(减少编译依赖性,访问/可见性控制,额外功能,代码重用机会......)