1

I'm learning to use std::any and std::any_cast, it's not working as I expected: after casting, the internal shared_ptr of casted object becomes empty.

Here is the simplified code to show the issue, it's hard for me to reason about why after inserting the pointer into the map, the pointed object has changed: shared_ptr no longer points to the original value and becomes empty. I'd love to learn why this behavior happens!

Note I am casting to pointer AA* and store the pointer in an unsorted map, since in real world case I cannot copy AA object, it's a workaround.

Thanks a lot!

struct AA {
  std::shared_ptr<int> m_ptr = std::make_shared<int>(22);
  int get() {
    return *m_ptr;
  }
};

struct Cache {
  std::unordered_map<std::string, std::any> map;

  void insert(std::string str) {
    auto a_ptr = std::make_unique<AA>();
    map[str] = std::make_any<AA*>(a_ptr.get());

    // this casting works as expected, output is 22
    auto casted = std::any_cast<AA*>(map[str]);
    std::cout <<"inside insert: " << casted->get() << std::endl;
    }
};

int main(int argc, char** argv) {
    Cache cache;
    cache.insert("aa");
    auto data = std::any_cast<AA*>(cache.map["aa"]);
    // this won't work, output is 0, why?
    std::cout << "in main: " << data->get() << std::endl;
}

godbolt demo

Jinsong Li
  • 6,347
  • 1
  • 23
  • 20
  • 10
    When `insert` ends, `a_ptr` is destroyed, which destroys the object it points to. That's the whole purpose of `unique_ptr`, it cleans up after itself on scope exit. – NathanOliver May 03 '22 at 23:56
  • Thanks @NathanOliver! I do want to keep `a_ptr`'s lifetime the same as `map`, but I cannot make it a class member, wondering if there is any idea to make it work? – Jinsong Li May 03 '22 at 23:57
  • 3
    @JinsongLi don't store the raw `AA*` pointer, you would have to store the `unique_ptr` instead. But `std::any` can only hold copy-constructible types, and `unique_ptr` is not copy-constructible ([see this](https://stackoverflow.com/questions/46852168/)), so you will likely have to use `shared_ptr` instead. – Remy Lebeau May 03 '22 at 23:59
  • Thanks @RemyLebeau! by storing `a_ptr` in the map, it won't be destroyed when `insert` ends, right? I'll test it out! – Jinsong Li May 04 '22 at 00:04
  • 4
    @JinsongLi in this particular example, you don't really need to construct the `AA` object *dynamically* at all, since `AA` is itself copyable: `map[str] = AA{}; ... auto data = std::any_cast(map[str]);` – Remy Lebeau May 04 '22 at 00:05
  • The reason is that `AA` is not copy constructible and no move constructor, so it cannot be directly constructed through `std::any`, so I figure maybe I just dynamically create `AA` object and then store its pointer, which can be copied freely. – Jinsong Li May 04 '22 at 00:10
  • Thanks a lot @RemyLebeau for the code snippet! Will try out tonight :) – Jinsong Li May 04 '22 at 00:13
  • 2
    *"I cannot copy AA object,"* -- this is good to mention. To reinforce this point / as a reminder, I would add `AA(const AA&) = delete; // non-copyable` to your class definition. (Or change `m_ptr` from a `shared_ptr` to a `unique_ptr`, which would also make the class non-copyable.) – JaMiT May 04 '22 at 02:10

0 Answers0