2

I have declared serialization class with the following methods, but did not implemented it yet:

    const std::string toJson(const Location &obj);

    const std::string toJson(std::weak_ptr<const Location> obj);

    const std::string toJson(std::shared_ptr<const Location> obj);

The job of the serialization class is, no surprise, to convert between one representation to another, e.g. from Json to the C++ class and vice-versa.

Now somewhere else in my code, I take unique ownership like this:

    std::unique_ptr<Location> uniqueLocation = getLocation();

Now my question is: The serialization does not need to take ownership of the unique_ptr. Which method should it call? The toJson using weak_ptr, or the toJson by reference?

Hatted Rooster
  • 35,759
  • 6
  • 62
  • 122
benjist
  • 2,740
  • 3
  • 31
  • 58
  • 2
    If you don't intend to store the argument somewhere then I'd say `const &`. – Hatted Rooster Jun 12 '19 at 18:13
  • You may find this Herb Sutter blog post relevant: https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/ – Eljay Jun 12 '19 at 18:20
  • You can't get a `unique_ptr` from a `weak_ptr` because a `weak_ptr` implies the object is already owned by one or more `shared_ptr`. An object can't be owned by both a `unique_ptr` and ` shared_ptr` at the same time. – François Andrieux Jun 12 '19 at 18:26

3 Answers3

4

You can't create a std::shared_ptr from a std:::unique_ptr (unless you release() its ownership first), and you can't create a std::weak_ptr from a std::unique_ptr at all, only from a std::shared_ptr. So that leaves only one option:

const std::string toJson(const Location &obj);

Which makes sense, as you don't need or want toJson() to take/share ownership of the Location object, just to use it as-is.

To be honest, the other overloads don't really make much sense to have at all. The caller should decide how it needs to obtain a reference to a Location object, and then pass the actual object to toJson() as needed, eg:

std::unique_ptr<Location> uniqueLocation = getLocation();
std::string s = toJson(*uniqueLocation);

But, if you want to keep the overloads, just have them delegate to the one overload that takes an object reference:

const std::string toJson(const Location &obj)
{
    // do the actual work here...
}

const std::string toJson(std::weak_ptr<Location> obj)
{
    std::shared_ptr<Location> sp = obj.lock();
    return toJson(sp);
}

const std::string toJson(const std::shared_ptr<Location> &obj)
{
    std::string s;
    if (obj)
        s = toJson(*obj);
    return s;
}

const std::string toJson(const std::unique_ptr<Location> &obj)
{
    std::string s;
    if (obj)
        s = toJson(*obj);
    return s;
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
3

weak_ptr is the companion to shared_ptr, and won't help you here.

You can use the first overload, or pass the unique_ptr itself by reference.

Even if you were using a shared_ptr rather than a unique_ptr, I'd advise the same. I've stored weak_ptrs in containers before (or captured them in lambdas), but if you're just passing into a function that needs to do some work, it seems like unnecessary overhead.

Herb's opinion on the subject may be of interest to you.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
0

Smart pointers are about ownership.

Ownership is about lifetime.

const std::string toJson(const Location &obj);

This says "you will pass an obj which is a valid Location, and this function promises not to change it".

The return value -- const std::string -- is a toxic anti-pattern. Don't do that. Return std::string.

std::string toJson(std::weak_ptr<const Location> obj);

This states "I will take a possibly valid reference to an obj, which I will faithfully store somewhere. At some unknown future point, I will check if that object still exists, and create a shared pointer to it. Once you call this function, do not assume that the lifetime of the pointed to obj will end at any controlled spot; but usually I'll be polite, and not hold onto a generated shared_ptr for more than a short bit".

std::string toJson(std::shared_ptr<const Location> obj);

This says "I want to participate in the shared management of the pointed to obj. obj has and should have a complex lifetime which cannot be easily expressed through simple ownership. I tried to use simpler ownership models, but they don't reflect the true essence of how long obj should survive, so I'm using reference counted pointers."

std::unique_ptr<Location> uniqueLocation = getLocation();

This says "I want to have controlled ownership of this object, which has never been in a complex ownership situation. If I could, I'd be a value (or maybe an optional), but some issues block me from being a value. So I'll be a unique pointer to a value."

The serialization does not need to take ownership of the unique_ptr. Which method should it call? The toJson using weak_ptr, or the toJson by reference?

A function that converts things to Json probably just needs the object being converted to be valid the length of the call.

toJson(const Location&) is the one that means that.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Can you explain why not to return const std::string? – benjist Jun 12 '19 at 19:05
  • 1
    @benjist Sure; it does nothing particularly useful, and can inhibit move construction. It is true that it blocks some bad use of `operator=`, but the loss of move-construction efficiency far outweights that. – Yakk - Adam Nevraumont Jun 12 '19 at 19:26
  • I've created a new question here https://stackoverflow.com/questions/56570452/is-returning-a-const-stdstring-really-slower-than-non-const – benjist Jun 12 '19 at 21:15