How to implement a C++ method that creates a new o

2019-06-25 23:48发布

I have a C++ AuthenticatingProxy class instance with the below method. This method creates a Response object, which is then updated with state, and then returned. Because of the internals of the Response object it cannot be copied (i.e. I cannot simply return by value).

const Response& AuthenticatingProxy::Get(const std::string& host,
                                  const std::string& path,
                                  const http_headers& headers)
{
  static Response response;
  // do the HTTP call, and set response's state here
  return response;
}

Many calls can occur on this method in the AuthenticatingProxy class, so having a static variable in here is not ideal. Could someone please suggest a solution that returns a reference that is not destroyed on the function exiting? Thanks.

The nearest I've got in my research are best practices pages that mention "Return by reference, not value, where a large object is created" but I'm yet to find an example! All the examples are for references that are passed in and then returned, or for int& style references - not ones where the object instances are created within the function itself.

I think the solution may exist in having the function return response as a value, but in the calling code using a const variable to capture it. Not sure if that only works for int though - all examples I find use basic types. Any help greatly appreciated.

3条回答
你好瞎i
2楼-- · 2019-06-26 00:11

There are three storage classes in C++. Static, automatic and dynamic.

Automatic objects are destroyed when the scope is exited, so you must not return a reference to a local automatic variable.

You can return a reference to a static, but as you say, it's not appropriate for you.

The only option left is dynamic. However, dynamic objects must be destroyed somehow, and when you return a reference to a dynamically created object, it's not clear to the caller that it should take care of it's destruction. So, this technically possible, but there is no good solution to the memory management. Also, dynamic allocation can be expensive.

Not when you insist returning a reference. But if you return a std::unique_ptr instead, this problem disappears.

Let's revisit the option of automatic variables then. I said that you must not return a reference to a local automatic variable. It's perfectly OK to return a reference to an automatic variable from higher scope. How do you access such variable? Using a reference of course!

const Response& AuthenticatingProxy::Get(const std::string& host,
                                  const std::string& path,
                                  const http_headers& headers,
                                  Response& response)
{
  // do the HTTP call, and set response's state here
  return response; // might as well return void because caller already has a reference
}

This is a typical solution when dynamic allocation is too expensive and when the compiler does not support move semantics. In this solution, no object is actually created in the function. It simply sets the data for an object created by the caller. The object can actually have any storage type, it won't matter.


Best solution however, is to return by value. You say that Response is not copyable. But since C++11, that doesn't prevent returning it by value as long as the type is movable. Even the move is elided by most compilers.

Response response;
// do stuff to response
return response;
查看更多
对你真心纯属浪费
3楼-- · 2019-06-26 00:12

Because of the internals of the Response object it cannot be copied (i.e. I cannot simply return by value).

You are describing a problem that existed in C++03, but which no longer exists.

Take for example the old loved std::fstream.
Basically, deep inside*, it held a file descriptor/handle that is used to read/write from/to the file.
Because of the nature and design of C++, the fstream destructor closed that file handle in order to clean up the object and prevent handle leaks.

Because of the internals of the fstream object, it could not be returned by value. Returning it by value meant to somehow prevent the destructor from closing that file handle, or to make the copy constructor duplicate that handle in some sort. Having cross platform solution is extremly difficult, if not impossible. Not to mention that copying the internal buffer is just plain wrong.

So up until C++11, you couldn't return fstream by value.
Then move semantics was invented.

In C++11, you can just move the object instead of copying it. In the fstream example, the move constructor shallow copies the file handle, while invalidating the original file-handle pointer. The original object destructor would inspect the original file-handle, see that it is invalid and skip closing it.

This is the idiomatic solution for your problem. While you cannot copy the object, you can definitely implement move semantics for it. The internals of the object will be moved to the return value while the original object remains "empty" in some sort.

Good explanations of move semantics can be found in this SO answer: What are move semantics?

If that solution is not possible as well, declare the object in the dynamic memory storage (the "heap") and return it by some smart pointer.

How to implement a C++ method that creates a new object, and returns a reference to it

Don't ever do it even if you can.

*yes, the file handle can be stored in the streambuff object instead, I'm describing the problem in a nutshell.

查看更多
唯我独甜
4楼-- · 2019-06-26 00:18

Thanks to all your great hints, I've found one answer...

(Although as others have mentioned, it leaves the caller with the problem of memory management. This is fine for my needs.)

This is described on this page: http://www.codeproject.com/Articles/823658/Using-auto-for-declaring-variables-of-move-only-ty

Here is my new method declaration:-

const Response* AuthenticatingProxy::Get(const std::string& host,
                                  const std::string& path,
                                  const http_headers& headers)
{
  auto && response = new Response;
  // do stuff to response
  return std::move(response);
}

Thus I'm returning a pointer, but using std::move and auto in the above manner returns me a pointer that I can manage in my calling code.

EDIT:-

Ok so based on feedback, I now have this in AuthenticatingProxy.cpp:-

std::unique_ptr<Response> AuthenticatingProxy::Get(const std::string& host,
                                  const std::string& path,
                                  const http_headers& headers)
{
  Response* response = new Response;
  // do call and fill out the response
  return std::unique_ptr<Response>(response);
}

My intermediate Connection class is doing this:-

std::unique_ptr<Response> Connection::getDocument(const std::string& uri) {
  return Connection::_proxy.Get(_serverUrl, "/v1/documents?uri=" + uri); 
}

(Note: I've had to remove the const-ness from my proxy and connection classes otherwise I'd get a compilation issue about using a deleted copy constructor)

And my client code (main) is doing this:-

    // MUST keep local reference to unique_ptr for this to work!!!
    const std::unique_ptr<Response> rp = connection->getDocument(uri); 
    Response* response = rp.get();
    ...
    std::cout << "This is JSON doc " << uri << ": " << std::endl << response->Json() << std::endl;

This seems to work fine, and incorporates all the advice on this thread. Is this now correct? If so, thanks everyone for your help!

查看更多
登录 后发表回答