【问题标题】:Is it possible to "fill" a function argument, which defaults to null, with an object?是否可以用对象“填充”默认为 null 的函数参数?
【发布时间】:2016-03-20 16:43:07
【问题描述】:

说我有这个方法:

bool match( /* some optional parameter */ );

这将做一些字符串模式匹配,我想允许它被赋予一个可选参数,当方法match()返回true时,该参数只会填充一个已知类型的实例(Match),这样的事情可能吗?

在 PHP 中我可以这样做:

public function match( Match &$match = null ) {
  if( someMatchingRoutineMatched() ) {
    $match = new Match();
    return true;
  }

  return false; // $match will stay null
}

然后这样称呼它:

// $test is some instance of the class that implements match()
// I don't have to declare $m up front, since it will be filled by reference
if( $test->match( $m ) ) {
  // $m would be filled with an instance of Match
}
else {
  // $m would be null
}

在 c++ 中是否有类似的可能?

我已经让它与以下内容一起工作

bool match( Match*& match ) {
  if( /* something matches */ ) {
    match = new Match;
    return true;
  }

  return false;
}

...然后这样称呼它:

Match* m = nullptr; // I wish I wouldn't have to declare this upfront as a nullptr
if( test.match( m ) ) {
  // a match occured, so m should no longer be a null pointer
  // but let's just make sure
  if( m != nullptr ) {
    // do something useful with m and afterwards delete it
    delete m;
  }
}

...然而,这一切都感觉有点麻烦。此外,我似乎不允许将参数设为可选,例如:

bool match( Match*& match = nullptr );

...因为我相信引用不允许为空,对吗?

我希望您能看到我正在努力实现的目标,并希望您能就我如何实现我的目标提供一些见解,如果有可能的话。

【问题讨论】:

  • 有两个重载,一个什么都不带,另一个和你一样,但没有默认值。
  • @DanMašek 好的,这将解决可选位,但是当match() 返回false 时,是否仍有办法将match 保持为null?基本上,我想要实现的是保证match确实找到匹配项时填充Match 的实例,并且否则为null。我希望这是有道理的。
  • 是的,请看下面的答案。
  • C++PHP 中,您可以返回NULL 而不是false 和新对象而不是true。如果方法返回的值不是NULL,那么调用代码可以将它存储到您传递给match() 的变量中。这样,当$m 的值发生变化时,读者就很清楚了(现在必须阅读函数 match() 的代码才能了解这一事实。)

标签: c++ pass-by-reference optional-parameters nullptr


【解决方案1】:

从“Match*”类型的右值初始化“Match*&”类型的非常量引用无效

Match*& match = nullptr 是不允许的,因为非 const 引用不能绑定到临时变量,并且在此处传递 nullptr 会创建一个临时 Match*


您可以返回指针,而不是传递对非常量的引用:

Match* match() {
  if( /* something matches */ ) {
    return new Match;
  }

  return nullptr;
}

现在nullptr 返回值表示没有匹配项,非nullptr 表示找到匹配项:

if( Match* m = test.match() ) { // nullptr means false, non-nullptr means true
  if( m != nullptr ) { // always true here
    // do something useful with m and afterwards delete it
    delete m;
  }
}

或者你可以使用@DanMašek 提到的重载:

bool match() {
  Match* m = nullptr;
  bool result = match(m);
  delete m; // deleting a nullptr is a no-op
  return result;
}

最后但同样重要的是,强制使用-unique_ptr-over-raw-owning-pointer,所以你不必担心delete,不用阅读match的文档就清楚是否返回的指针是拥有还是非拥有:

unique_ptr<Match> match() {
  if( /* something matches */ ) {
    return make_unique<Match>( /* constructor arguments */ );
  }

  return nullptr;
}

if( auto m = test.match() ) { // m deduced to be of type unique_ptr<Match>
  if( m != nullptr ) { // always true here
    // do something useful with m and afterwards delete it
    // no need to delete explicitly
  }
}

【讨论】:

  • 是的,返回 nullptr 肯定是我会考虑的一个选项,但是,我有点喜欢带有可选参数的显式 bool。正如我在其他地方评论过的那样,我有点惊讶地看到,我想到的这种风格并不像我认为的那样在 C 或 C++ 中无处不在。我认为 PHP 从 C 中借鉴了这种风格(因为许多 PHP 函数类似于 C API)。无论如何,也感谢您提供非常宝贵的意见。
  • 我认为它在 C 中比在 C++ 中更普遍,正如 Christian Hackl 的回答中所指出的那样。在现代 C++(C++11 及更高版本)中,很少需要输出参数。为所有必要的输出使用返回值也使界面更简单。
  • 正确!似乎我有很多材料要考虑,以便找出最适合我的意图以及在 C++ 上下文中什么最有意义。你们都非常有帮助。再次感谢!
  • @DecentDabbler 如果您不喜欢直接处理指针,您总是可以将处理包装在一个重载operator bool 的类中并增加您需要的其他操作。与 Cristian Hackl 的建议差不多。
【解决方案2】:

指向指针的指针、对指针的引用或可选的“out”参数都是 C 风格的编码和/或在 C++ 中不是很惯用。

当没有明确需要时,也不会使用动态分配 (new Match)。

对于您的问题,更类似于 C++ 的解决方案是 boost::optional,它是有时称为“Fallible”的 C++ 习语的实现。这个想法是返回一个带有两个成员变量的对象,一个表示成功,另一个是“真正的”返回值,以防第一个为真。

你也可以自己实现一个基本版本,当我们这样做的时候,用一个普通的实例替换指针,因为Match听起来不像是一个需要动态分配实例的类[* ].

struct Result
{
    bool success;
    Match match;
};

更精细的实现会将其转换为具有私有成员变量和公共成员函数的类,确保在 success 为 false 时访问 match 失败:

class Result
{
public:
    bool success() const { return m_success; }
    Match match() const {
        assert(m_success);
        return m_match;
    }

    Result() : m_success(false), m_match() {}
    Result(Match const& match) : m_success(true), m_match(match) {}

private:
    bool m_success;
    Match m_match;
};

match() 函数本身将如下所示:

Result match()
{
    if (someMatchingRoutineMatched()) {
        return Result(Match(some_match_arguments));
    }

    return Result();
}

然后调用者会这样使用它:

auto const result = match();
if (result.success())
{
    // use result.match()
}

如果你在类中添加bool 转换运算符,即:

    explicit operator bool() const
    {
        return m_success;
    }

那么你也可以将if写成如下,让它看起来更像你原来的PHP代码,虽然我不认为这样更具可读性,我建议不要这样做:

if (auto const result = match()) {
    // use result.match()
} else {
    // access to result.match() neither allowed nor needed
}

[*] 如果您确实需要动态分配,例如因为Match 是多态类层次结构的一部分,那么请考虑std::unique_ptr

【讨论】:

  • 由于我是 C++ 新手,我将不得不研究动态分配究竟需要什么,但我会弄清楚这一点。尽管您的解决方案比我想象的要复杂一些,但这是一种有趣的方法,我在 PHP 中也以类似的方式使用过,有时也是如此。因此,也感谢您提供有价值的替代方法。
  • @DecentDabbler:简而言之,动态分配发生在new,或者更高级别的智能指针,当内存中对象的生命周期不再绑定到源代码,这意味着它的析构函数在块结束时不会运行。这通常会使代码变得更复杂,但当然很少有程序可以在没有任何动态分配的情况下侥幸逃脱。裸news 是最严重的违规者。请参阅herbsutter.com/elements-of-modern-c-style(“智能指针:无删除”部分)
【解决方案3】:

如果@zenith 提出的错误,我非常喜欢返回nullptr 的c/c++ 风格。

但为了记录,这里还有另一种选择:

bool match( Match*& match ) {
  bool some_condition=true; 
  if( some_condition ) {
    match = new Match;
    return true;
  }
  match = nullptr;   // so that you don't need to set it upfront
  return false;
}

bool match() {       // here if you don't have any pointer to provide
    Match *forget_it; 
    return match(forget_it);
}

但是,有一个弱点,因为如果你总是在免费存储中创建一个新的匹配,你会泄漏内存。所以必须更新匿名版本:

bool match() {
    Match *forget_it; 
    bool rc=match(forget_it);
    delete forget_it;  // avoid leaking memory
    return rc;
}

不幸的是,普通 Match() 的调用者仍有可能忘记释放内存。所以我建议使用unique_ptr&lt;Match&gt;shared_ptr&lt;Match&gt; 而不是原始指针。

【讨论】:

  • 对! “忘记释放内存”也让我有点担心,但由于我是 C++ 新手,我必须研究 unique_ptrshared_ptr 究竟做了什么。感谢您非常宝贵的回答。
【解决方案4】:

如果match 成功,则处理此问题的首选方法可能是返回指向已创建对象的指针,否则返回nullptr,如 zenith 建议的那样。但是,如果您想要更类似于您在 PHP 中所做的事情,我建议您这样做:

#include <memory>

bool match(std::unique_ptr<Match>& ptr)
{
    bool some_condition = true;
    if(some_condition) {
        ptr = std::make_unique<Match>();
        return true;
    }
    else
        return false;
}

bool match()
{
    std::unique_ptr<Match> ptr;
    return match(ptr);
}

int main()
{
   match(); // if Match is created, it's automatically discarded
   std::unique_ptr<Match> ptr;
   if(match(ptr))
   {
       // do stuff with ptr
       // ptr will be deleted once it goes out of scope
   }

   return 0;
}

【讨论】:

  • 问题是,我假设这种风格在 C/C++ 中相当普遍,因为我认为 PHP 实际上是从 C 中借用了这种风格,因为 PHP 的函数主要来自 C API。也许我只是在想象一些事情。读到这种风格在 C/C++ 中并不常见,我有点惊讶。无论如何,你的回答似乎最接近我的初衷。我将不得不研究它(到目前为止的所有答案,就此而言)。但是,到目前为止,还要感谢您提供非常宝贵的意见!
  • @DecentDabbler C != C++,尤其是在代码风格方面:)
  • @DecentDabbler:真的没有“在 C/C++ 中相当普遍”的风格,因为它们是两种不同的语言,对于编码风格的好坏有着不同的哲学和想法。
猜你喜欢
  • 1970-01-01
  • 2014-07-30
  • 2016-07-03
  • 2010-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-16
相关资源
最近更新 更多