为什么我需要 C++ 中的 .* 运算符?
Why would I want a .* operator in C++?
我最近发现 C++ 中存在 .*
运算符(以及密切相关的 ->*
运算符)。 (参见 this 问题。)
起初看起来很整洁,但我为什么需要这样的东西?链接问题中的两个答案提供了可以从直接函数调用中受益的人为示例。
如果直接调用函数不方便,可以使用函数对象代替,例如 std::sort
中可能使用的 lambda 函数。这消除了间接级别,因此比使用 .*
.
性能更高
链接的问题还提到了这个例子的简化版本:
struct A {
int a;
int b;
};
void set_member(A& obj, int A::* ptr, int val){
obj.*ptr = val;
}
int main()
{
A obj;
set_member(obj, &A::b, 5);
set_member(obj, &A::a, 7);
// Both members of obj are now assigned
}
但是这样做很简单(也许是更好的做法,因为它更简洁并且不会不必要地限制 A
的成员)改为这样做:
struct A {
int a;
int b;
};
void set_me(int& out, int val){
out = val;
}
int main()
{
A obj;
set_me(obj.b, 5);
set_me(obj.a, 7);
// Both members of obj are now assigned
}
总而言之,指向成员函数的指针可以替换为函数对象,指向成员变量的指针可以替换为对所述变量或函数对象的直接引用。由于减少了一种间接性,这样做还可能会提高代码的效率。
This 问题仅提供了支持我的结论的示例,因此它没有回答我的问题。
除了连接使用 .*
的遗留代码(其中根本别无选择),真的 时,我想使用 .*
?
您可以创建指向成员的指针集合并迭代它们。例如:
struct UserStrings
{
std::string first_name;
std::string surname;
std::string preferred_name;
std::string address;
};
...
std::array<std::string UserStrings::*, 4> str_cols = { &UserStrings::first_name, &UserStrings::surname, &UserStrings::preferred_name, &UserStrings::address };
std::vector<UserStrings> users = GetUserStrings();
for (auto& user : users)
{
for (auto& column : str_cols)
{
SanitizeForSQLQuery(user.*column);
}
}
假设您想为 C++ 编写一个 LINQ 样式库,可以像这样使用:
struct Person
{
std::string first_name;
std::string last_name;
std::string occupation;
int age;
int children;
};
std::vector<Person> people = loadPeople();
std::vector<std::string> result = from(people)
.where(&Person::last_name == "Smith")
.where(&Person::age > 30)
.select("%s %s",&Person::first_name,&Person::last_name);
for(std::string person : result) { ... };
在幕后,where
函数接受包含指向成员(以及其他内容)的指针的表达式树,并应用于每个向量项以寻找匹配项。 select
语句接受格式字符串和一些指向成员的指针,并对通过 where
语句的任何矢量项进行 sprintf
样式格式化。
我写过这样的东西,还有其他几个写法略有不同 (Is there a LINQ library for C++?)。指向成员的指针允许库用户指定他们想要的结构的任何成员,而库不需要知道他们可能做什么。
用来实现std::mem_fn
,用来实现std::function
。
以下代码显示了 ->*
运算符如何在天真的 Function
class 实现中工作。
同样,您可以使用 .*
运算符和 class 引用来实现成员调用程序 class。
#include <iostream>
class A
{
public:
void greet()
{
std::cout << "Hello world"<<std::endl;
}
};
template<typename R, typename ...TArgs>
class Invoker
{
public:
virtual R apply(TArgs&& ...args) = 0;
};
template<typename C, typename R, typename ...TArgs>
class MemberInvoker :public Invoker<R, TArgs...>
{
protected:
C* sender;
R(C::*function)(TArgs ...args);
public:
MemberInvoker(C* _sender, R(C::*_function)(TArgs ...args))
:sender(_sender)
, function(_function)
{
}
virtual R apply(TArgs&& ...args) override
{
return (sender->*function)(std::forward<TArgs>(args)...);
}
};
template<typename T>
class Func
{
};
template<typename R, typename ...TArgs>
class Func<R(TArgs...)>
{
public:
Invoker<R,TArgs...>* invoker=nullptr;
template<typename C>
Func(C* sender, R(C::*function)(TArgs...))
{
invoker =new MemberInvoker<C, R, TArgs...>(sender, function);
}
R operator()(TArgs&& ...args)
{
return invoker->apply(std::forward<TArgs>(args)...);
}
~Func()
{
if (invoker)
{
delete invoker;
invoker = nullptr;
}
}
};
int main()
{
A a;
Func<void()> greetFunc(&a, &A::greet);
greetFunc();
system("PAUSE");
}
您的示例过于简单,无法说明问题。考虑稍微复杂一点的
struct A {
int a;
int b;
};
void set_n_members(A objs[], unsigned n, int A::* ptr, int val)
{
for (unsigned i = 0; i < n; ++i)
objs[i].*ptr = val;
}
int main()
{
A objs[100];
set_n_members(objs, 100, &A::b, 5);
set_n_members(objs, 100, &A::a, 7);
}
如果没有 int A::* ptr
并且不会引起代码膨胀,您将如何重写它?
我最近发现 C++ 中存在 .*
运算符(以及密切相关的 ->*
运算符)。 (参见 this 问题。)
起初看起来很整洁,但我为什么需要这样的东西?链接问题中的两个答案提供了可以从直接函数调用中受益的人为示例。
如果直接调用函数不方便,可以使用函数对象代替,例如 std::sort
中可能使用的 lambda 函数。这消除了间接级别,因此比使用 .*
.
链接的问题还提到了这个例子的简化版本:
struct A {
int a;
int b;
};
void set_member(A& obj, int A::* ptr, int val){
obj.*ptr = val;
}
int main()
{
A obj;
set_member(obj, &A::b, 5);
set_member(obj, &A::a, 7);
// Both members of obj are now assigned
}
但是这样做很简单(也许是更好的做法,因为它更简洁并且不会不必要地限制 A
的成员)改为这样做:
struct A {
int a;
int b;
};
void set_me(int& out, int val){
out = val;
}
int main()
{
A obj;
set_me(obj.b, 5);
set_me(obj.a, 7);
// Both members of obj are now assigned
}
总而言之,指向成员函数的指针可以替换为函数对象,指向成员变量的指针可以替换为对所述变量或函数对象的直接引用。由于减少了一种间接性,这样做还可能会提高代码的效率。
This 问题仅提供了支持我的结论的示例,因此它没有回答我的问题。
除了连接使用 .*
的遗留代码(其中根本别无选择),真的 时,我想使用 .*
?
您可以创建指向成员的指针集合并迭代它们。例如:
struct UserStrings
{
std::string first_name;
std::string surname;
std::string preferred_name;
std::string address;
};
...
std::array<std::string UserStrings::*, 4> str_cols = { &UserStrings::first_name, &UserStrings::surname, &UserStrings::preferred_name, &UserStrings::address };
std::vector<UserStrings> users = GetUserStrings();
for (auto& user : users)
{
for (auto& column : str_cols)
{
SanitizeForSQLQuery(user.*column);
}
}
假设您想为 C++ 编写一个 LINQ 样式库,可以像这样使用:
struct Person
{
std::string first_name;
std::string last_name;
std::string occupation;
int age;
int children;
};
std::vector<Person> people = loadPeople();
std::vector<std::string> result = from(people)
.where(&Person::last_name == "Smith")
.where(&Person::age > 30)
.select("%s %s",&Person::first_name,&Person::last_name);
for(std::string person : result) { ... };
在幕后,where
函数接受包含指向成员(以及其他内容)的指针的表达式树,并应用于每个向量项以寻找匹配项。 select
语句接受格式字符串和一些指向成员的指针,并对通过 where
语句的任何矢量项进行 sprintf
样式格式化。
我写过这样的东西,还有其他几个写法略有不同 (Is there a LINQ library for C++?)。指向成员的指针允许库用户指定他们想要的结构的任何成员,而库不需要知道他们可能做什么。
用来实现std::mem_fn
,用来实现std::function
。
以下代码显示了 ->*
运算符如何在天真的 Function
class 实现中工作。
同样,您可以使用 .*
运算符和 class 引用来实现成员调用程序 class。
#include <iostream>
class A
{
public:
void greet()
{
std::cout << "Hello world"<<std::endl;
}
};
template<typename R, typename ...TArgs>
class Invoker
{
public:
virtual R apply(TArgs&& ...args) = 0;
};
template<typename C, typename R, typename ...TArgs>
class MemberInvoker :public Invoker<R, TArgs...>
{
protected:
C* sender;
R(C::*function)(TArgs ...args);
public:
MemberInvoker(C* _sender, R(C::*_function)(TArgs ...args))
:sender(_sender)
, function(_function)
{
}
virtual R apply(TArgs&& ...args) override
{
return (sender->*function)(std::forward<TArgs>(args)...);
}
};
template<typename T>
class Func
{
};
template<typename R, typename ...TArgs>
class Func<R(TArgs...)>
{
public:
Invoker<R,TArgs...>* invoker=nullptr;
template<typename C>
Func(C* sender, R(C::*function)(TArgs...))
{
invoker =new MemberInvoker<C, R, TArgs...>(sender, function);
}
R operator()(TArgs&& ...args)
{
return invoker->apply(std::forward<TArgs>(args)...);
}
~Func()
{
if (invoker)
{
delete invoker;
invoker = nullptr;
}
}
};
int main()
{
A a;
Func<void()> greetFunc(&a, &A::greet);
greetFunc();
system("PAUSE");
}
您的示例过于简单,无法说明问题。考虑稍微复杂一点的
struct A {
int a;
int b;
};
void set_n_members(A objs[], unsigned n, int A::* ptr, int val)
{
for (unsigned i = 0; i < n; ++i)
objs[i].*ptr = val;
}
int main()
{
A objs[100];
set_n_members(objs, 100, &A::b, 5);
set_n_members(objs, 100, &A::a, 7);
}
如果没有 int A::* ptr
并且不会引起代码膨胀,您将如何重写它?