14

これはおそらく哲学的な質問ですが、次の問題に遭遇しました。

std::function を定義し、それを正しく初期化しないと、アプリケーションは次のようにクラッシュします。

typedef std::function<void(void)> MyFunctionType;
MyFunctionType myFunction;
myFunction();

関数が引数として渡される場合、次のようになります。

void DoSomething (MyFunctionType myFunction)
   {
   myFunction();
   }

もちろん、それもクラッシュします。これは、次のようなチェック コードを追加する必要があることを意味します。

void DoSomething (MyFunctionType myFunction)
   {
   if (!myFunction) return;
   myFunction();
   }

これらのチェックを要求すると、すべてのポインター引数を明示的にチェックする必要もあった古い C 時代にフラッシュバックします。

void DoSomething (Car *car, Person *person)
   {
   if (!car) return;      // In real applications, this would be an assert of course
   if (!person) return;   // In real applications, this would be an assert of course
   ...
   }

幸いなことに、C++ で参照を使用できるため、これらのチェックを書くことができません (呼び出し元が nullptr の内容を関数に渡さなかったと仮定すると:

void DoSomething (Car &car, Person &person)
   {
   // I can assume that car and person are valid
   }

では、なぜ std::function インスタンスにデフォルトのコンストラクターがあるのでしょうか? デフォルトのコンストラクターがなければ、関数の他の通常の引数と同様に、チェックを追加する必要はありません。また、「オプションの」 std::function を渡したい「まれな」ケースでは、ポインタを渡すこともできます (または boost::optional を使用します)。

4

7 に答える 7

19

True, but this is also true for other types. E.g. if I want my class to have an optional Person, then I make my data member a Person-pointer. Why not do the same for std::functions? What is so special about std::function that it can have an 'invalid' state?

It does not have an "invalid" state. It is no more invalid than this:

std::vector<int> aVector;
aVector[0] = 5;

What you have is an empty function, just like aVector is an empty vector. The object is in a very well-defined state: the state of not having data.

Now, let's consider your "pointer to function" suggestion:

void CallbackRegistrar(..., std::function<void()> *pFunc);

How do you have to call that? Well, here's one thing you cannot do:

void CallbackFunc();
CallbackRegistrar(..., CallbackFunc);

That's not allowed because CallbackFunc is a function, while the parameter type is a std::function<void()>*. Those two are not convertible, so the compiler will complain. So in order to do the call, you have to do this:

void CallbackFunc();
CallbackRegistrar(..., new std::function<void()>(CallbackFunc));

You have just introduced new into the picture. You have allocated a resource; who is going to be responsible for it? CallbackRegistrar? Obviously, you might want to use some kind of smart pointer, so you clutter the interface even more with:

void CallbackRegistrar(..., std::shared_ptr<std::function<void()>> pFunc);

That's a lot of API annoyance and cruft, just to pass a function around. The simplest way to avoid this is to allow std::function to be empty. Just like we allow std::vector to be empty. Just like we allow std::string to be empty. Just like we allow std::shared_ptr to be empty. And so on.

To put it simply: std::function contains a function. It is a holder for a callable type. Therefore, there is the possibility that it contains no callable type.

于 2011-09-23T09:50:14.400 に答える
13

Actually, your application should not crash.

§ 20.8.11.1 Class bad_function_call [func.wrap.badcall]

1/ An exception of type bad_function_call is thrown by function::operator() (20.8.11.2.4) when the function wrapper object has no target.

The behavior is perfectly specified.

于 2011-09-23T09:48:02.247 に答える
6

の最も一般的な使用例の1つは、std::function特定の条件が満たされたときに呼び出されるコールバックを登録することです。初期化されていないインスタンスを許可すると、必要な場合にのみコールバックを登録できます。そうしないと、少なくとも何らかのno-op関数を常に渡す必要があります。

于 2011-09-23T09:31:56.203 に答える
5

答えはおそらく歴史的なものです:std::functionは関数ポインターの代替として意図されており、関数ポインターにはNULL. したがって、関数ポインターに簡単な互換性を提供したい場合は、無効な状態を提供する必要があります。

boost::optionalあなたが述べたように、その仕事はうまくいくので、識別可能な無効な状態は実際には必要ありません。だから私はそれstd::functionが歴史のためにそこにあると言うでしょう.

于 2011-09-23T09:44:16.333 に答える
1

構築時にすべてを初期化できない場合があります(たとえば、パラメータが別の構築への影響に依存し、次に最初の構築への影響に依存する場合...)。

この場合、後で修正するために識別可能な無効な状態を認めて、必然的にループを中断する必要があります。したがって、最初の要素を「null」として作成し、2番目の要素を作成して、最初の要素を再割り当てします。

実際には、チェックを回避できます。関数が使用されている場合、関数を埋め込むオブジェクトのコンストラクター内で、有効な再割り当て後に常に戻ることを許可します。

于 2011-09-23T09:32:50.043 に答える
0

コールバックに std::function を使用するだけです。空でない場合は引数をハンドラーに転送する単純なテンプレート ヘルパー関数を使用できます。

template <typename Callback, typename... Ts>
void SendNotification(const Callback & callback, Ts&&... vs)
{
    if (callback)
    {
        callback(std::forward<Ts>(vs)...);
    }
}

そして、次のように使用します。

std::function<void(int, double>> myHandler;
...
SendNotification(myHandler, 42, 3.15);
于 2015-09-26T08:25:50.263 に答える
0

In the same way that you can add a nullstate to a functor type that doesn't have one, you can wrap a functor with a class that does not admit a nullstate. The former requires adding state, the latter does not require new state (only a restriction). Thus, while i don't know the rationale of the std::function design, it supports the most lean & mean usage, no matter what you want.

Cheers & hth.,

于 2011-09-23T09:50:10.303 に答える