在C ++中实现any_of
代码审查 | 2021-01-11 | 编程黑洞网 | | 674 人阅读
我决定在C ++中实现Python的
any
。我使用模板来允许传递多种类型的数据,而不是多次重载该函数。这是我第一次使用模板,因此我非常希望获得有关模板使用情况的反馈。我对引用和指针也很陌生,所以我也想对我对它们的使用提出一些批评。当然,桌上还有其他东西值得赞赏。
编写此程序时,我意识到
std::any_of
存在。所以,是的,我确实知道已经有一个内置的方法。
函数:
main.cpp
#ifndef ANY_HPP_INCLUDED
#define ANY_HPP_INCLUDED
/**
* @author Ben Antonellis
**/
#include <vector>
#include <iostream>
/**
* Returns True if any of the elements meet the callback functions parameters.
*
* @param elements - A list of elements.
* @param callback - Callback function to invoke on each element.
*
* @return bool - True if parameters are met, False otherwise.
**/
template <typename List, typename Function>
bool any(List &elements, Function *callback) {
for (auto element : elements) {
if (callback(element)) {
return true;
}
}
return false;
}
#endif
如果要编译和运行,下面是我正在使用的脚本进行编译和运行此程序。
评论
与主题无关,但请注意,就我而言,cppreference是比cplusplus.com更新的(通常)是社区驱动的高质量文档。
同意,我几乎专门使用cppreference。它不是很漂亮,但是可以完成工作:)
#1 楼
该算法看起来正确。
关于函数签名,我将进行三处更改:
您没有修改元素,因此请使用常量引用而不是参考。
您不需要指定指向Function的指针,Function已经是模板参数,并且非指针可能是有效的(例如:带有()运算符的类)。
在这种情况下,“回调”并不是一个非常有用的名称。 “谓词”会更好。
虽然这种形式很好,但我还将包括一个使用开始/结束迭代器而不是容器的版本。
此外,没有理由包括vector或在any.hpp中的iostream-仅包含必要的内容。
为了确保测试方法的完整性,您还应该使用空集合进行测试,并且没有匹配项。
#2 楼
我认为您作为初学者做得很好。除了Errorsatz所说的:
还考虑将element
用作const引用以防止不必要的复制:const auto& element : elements
您在测试文件中错过了#include <string>
。
std::endl
刷新缓冲区并导致性能下降;除非需要冲洗行为,否则请使用\n
。
尽量避免特定于编译器的非标准扩展,因为它们会使您的程序不可移植。 -std=c++20 -pedantic-errors
使g ++进入兼容模式并禁用这些非标准扩展。也不需要将编译步骤与链接步骤分开-g ++会为您完成。并记住要打开警告!它们可以帮助您发现程序中的许多逻辑错误。我通常的编译指令如下: >
#3 楼
我看到了一些问题,包括复制数据并通过引用或const引用传递。我是否建议您遵循STL使用迭代器的方法?
您的任何函数本质上都是std :: find_if使用容器而不是迭代器。
将其重写以使用
std::find_if
显示差异:
template<typename List, typename Predicate>
bool any(List&& list, Predicate&& pred)
{
return std::find_if(
begin(list),
end(list),
std::forward<Predicate>(pred))
!= end(list);
}
我在最初的实现中看到的一些问题是:
将列表作为非常量引用传递。如MSalters答案中所述,该问题已得到修复,如上所示。
将谓词作为指针传递。我们在这里使用模板,因此可以使谓词为所需的任何内容,而无需将其转换为函数指针。
重新实现
std::find_if
(尽管我知道想学习!)在没有
std::find_if
的情况下实现此方法是:
template<typename List, typename Predicate>
bool any(List&& list, Predicate&& pred)
{
for(auto&& element : list) {
if(pred(element))
return true;
}
return false;
}
#4 楼
您的标头防护为:
#ifndef ANY_HPP_INCLUDED
我在现代代码中最常看到的样式是
#ifndef INCLUDED_ANY_HPP
或者,只需省略
_HPP
部分)。原因是,从技术上讲,C和C ++会将所有与
E[A-Z].*
匹配的大写名称保留给实现,用于
EINVAL
和
EPERM
之类的宏。当然,在实践中,您的实现将没有名为
EGG_HPP_INCLUDED
的宏;但是编写
INCLUDED_EGG_HPP
并从根本上消除这种考虑在理论上更安全,也同样容易。不以
I
开头的名称保留给实现。它。考虑以下用例:
template <typename List, typename Function>
bool any(List &elements, Function *callback) {
由于
elements
是一个右值表达式,因此按左值引用获取的模板将不起作用。 br />同样,用指针取
getPrimesBetween(3, 100)
很奇怪。通过值(STL样式)或const引用进行回调(以避免不必要的复制)。考虑:
std::vector<int> getPrimesBetween(int, int);
bool isOdd(int);
bool b = any(getPrimesBetween(3, 100), isOdd);
这不能与您的模板一起编译,因为
callback
是与
decltype(isOdd)
模式不匹配的类类型(未命名的lambda类类型) 。
您以一种非常奇怪的非常可爱的方式解决了这个问题:
,而不是
Function *
。但是,后者也有效,因为lambda类型没有
&isOdd
,因此它进行了隐式转换为标量类型,因此可以使用内置的
*isOdd
。唯一可用的隐式转换是对函数指针类型的隐式转换。因此,您可以将lambda转换为函数指针,对该指针进行解引用以获得函数引用,然后将该函数按值传递给您的模板,该模板将其衰减回一个函数指针,该指针与
operator*
模式匹配。 >
在此处观察
*
和
Function *
之间的区别:https://godbolt.org/z/ehdbvw
具体来说,
&isOdd
和
*isOdd
具有不同的类型,但是
&isOdd
和
&isEven
具有相同的类型。
通过仅使用函数指针,还可以防止模板与有状态lambda一起使用:
std::vector<int> primes;
auto isOdd = [](int x) { return x % 2 != 0; };
bool b = any(primes, isOdd);
bool b = any(primes, *isOdd);
由于您不打算修改元素,因此可以通过
*isOdd
来接受它们;但是实际上您应该使用
*isEven
来接受它们,因为
const&
始终有效。 />
我个人可能会使用名称
auto&&
,
auto&&
和
Range
来代替
Callable
,
pred[icate]
和
List
……但是我想只是在书面上写得很清楚,我们当中的一个人也很喜欢长期处于STL行话不健康的水域中。 :)
#5 楼
Errorsatz已经提到过Functor
类型,但是我想为Functor&&
辩护。
对于测试,您实际上就是要测试边缘案例的情况。即其中第一个或最后一个元素具有所需的属性。要测试的另外两个特殊情况是单个元素匹配或不匹配。 (很多经验法则为零;您的测试都有很多= 3个元素)
#6 楼
我看到一个可以使用C ++注释的地方:
/* Test Any Implementation */
因为该注释仅占一行,所以我通常更喜欢使用C ++注释: >
// Test Any Implementation
评论
\ $ \ begingroup \ $
尽管谓词更具体,但“回调”似乎已经成为一个很常见的术语,指代传递给高阶函数的函数,无论其在算法中的作用如何。
\ $ \ endgroup \ $
– Barmar
20-3-25在16:29
\ $ \ begingroup \ $
@Barmar不,谓词既包含参数列表又包含返回类型,因此是惯用的。
\ $ \ endgroup \ $
–长颈鹿队长
20 Mar 26 '20 at 0:56
\ $ \ begingroup \ $
@Barmar:在说明中也要说谓语。如您所注意到的,回调是一个广义术语,在C ++谓词中,对于所有布尔(T)函子来说,谓词都是一致的。
\ $ \ endgroup \ $
– MSalters
20 Mar 26 '20 at 12:19
\ $ \ begingroup \ $
@Barmar在C ++(以及其他语言!)的上下文中,我希望回调主要由于其副作用而被调用。相比之下,除了其他注释外,我希望谓词在逻辑上没有副作用。
\ $ \ endgroup \ $
–康拉德·鲁道夫(Konrad Rudolph)
20 Mar 26 '20在21:04
\ $ \ begingroup \ $
@Barmar C ++术语与其他编程语言有很大的不同...在C ++中,“回调”通常被重新定义为表示在事件管理器中注册的对某些事件做出反应的函数/功能对象,或者在类似的上下文中注册操作由外部信号调用,主要是为了产生副作用; ios_base中的一个是一个示例。对于谓词更合适的情况使用回调对我(可能有偏见)受C ++训练的耳朵非常反C ++。
\ $ \ endgroup \ $
– L. F.
20 Mar 27 '20:40