webrtc 使用scoped_ptr

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef THIRD_PARTY_WEBRTC_FILES_TALK_BASE_MOVE_H_
#define THIRD_PARTY_WEBRTC_FILES_TALK_BASE_MOVE_H_

// Macro with the boilerplate that makes a type move-only in C++03.
//
// USAGE
//
// This macro should be used instead of DISALLOW_COPY_AND_ASSIGN to create
// a "move-only" type.  Unlike DISALLOW_COPY_AND_ASSIGN, this macro should be
// the first line in a class declaration.
//
// A class using this macro must call .Pass() (or somehow be an r-value already)
// before it can be:
//
//   * Passed as a function argument
//   * Used as the right-hand side of an assignment
//   * Returned from a function
//
// Each class will still need to define their own "move constructor" and "move
// operator=" to make this useful.  Here's an example of the macro, the move
// constructor, and the move operator= from the scoped_ptr class:
//
//  template <typename T>
//  class scoped_ptr {
//     TALK_MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue)
//   public:
//    scoped_ptr(RValue& other) : ptr_(other.release()) { }
//    scoped_ptr& operator=(RValue& other) {
//      swap(other);
//      return *this;
//    }
//  };
//
// Note that the constructor must NOT be marked explicit.
//
// For consistency, the second parameter to the macro should always be RValue
// unless you have a strong reason to do otherwise.  It is only exposed as a
// macro parameter so that the move constructor and move operator= don't look
// like they're using a phantom type.
//
//
// HOW THIS WORKS
//
// For a thorough explanation of this technique, see:
//
//   http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Move_Constructor
//
// The summary is that we take advantage of 2 properties:
//
//   1) non-const references will not bind to r-values.
//   2) C++ can apply one user-defined conversion when initializing a
//      variable.
//
// The first lets us disable the copy constructor and assignment operator
// by declaring private version of them with a non-const reference parameter.
//
// For l-values, direct initialization still fails like in
// DISALLOW_COPY_AND_ASSIGN because the copy constructor and assignment
// operators are private.
//
// For r-values, the situation is different. The copy constructor and
// assignment operator are not viable due to (1), so we are trying to call
// a non-existent constructor and non-existing operator= rather than a private
// one.  Since we have not committed an error quite yet, we can provide an
// alternate conversion sequence and a constructor.  We add
//
//   * a private struct named "RValue"
//   * a user-defined conversion "operator RValue()"
//   * a "move constructor" and "move operator=" that take the RValue& as
//     their sole parameter.
//
// Only r-values will trigger this sequence and execute our "move constructor"
// or "move operator=."  L-values will match the private copy constructor and
// operator= first giving a "private in this context" error.  This combination
// gives us a move-only type.
//
// For signaling a destructive transfer of data from an l-value, we provide a
// method named Pass() which creates an r-value for the current instance
// triggering the move constructor or move operator=.
//
// Other ways to get r-values is to use the result of an expression like a
// function call.
//
// Here's an example with comments explaining what gets triggered where:
//
//    class Foo {
//      TALK_MOVE_ONLY_TYPE_FOR_CPP_03(Foo, RValue);
//
//     public:
//       ... API ...
//       Foo(RValue other);           // Move constructor.
//       Foo& operator=(RValue rhs);  // Move operator=
//    };
//
//    Foo MakeFoo();  // Function that returns a Foo.
//
//    Foo f;
//    Foo f_copy(f);  // ERROR: Foo(Foo&) is private in this context.
//    Foo f_assign;
//    f_assign = f;   // ERROR: operator=(Foo&) is private in this context.
//
//
//    Foo f(MakeFoo());      // R-value so alternate conversion executed.
//    Foo f_copy(f.Pass());  // R-value so alternate conversion executed.
//    f = f_copy.Pass();     // R-value so alternate conversion executed.
//
//
// IMPLEMENTATION SUBTLETIES WITH RValue
//
// The RValue struct is just a container for a pointer back to the original
// object. It should only ever be created as a temporary, and no external
// class should ever declare it or use it in a parameter.
//
// It is tempting to want to use the RValue type in function parameters, but
// excluding the limited usage here for the move constructor and move
// operator=, doing so would mean that the function could take both r-values
// and l-values equially which is unexpected.  See COMPARED To Boost.Move for
// more details.
//
// An alternate, and incorrect, implementation of the RValue class used by
// Boost.Move makes RValue a fieldless child of the move-only type. RValue&
// is then used in place of RValue in the various operators.  The RValue& is
// "created" by doing *reinterpret_cast<RValue*>(this).  This has the appeal
// of never creating a temporary RValue struct even with optimizations
// disabled.  Also, by virtue of inheritance you can treat the RValue
// reference as if it were the move-only type itself.  Unfortunately,
// using the result of this reinterpret_cast<> is actually undefined behavior
// due to C++98 5.2.10.7. In certain compilers (e.g., NaCl) the optimizer
// will generate non-working code.
//
// In optimized builds, both implementations generate the same assembly so we
// choose the one that adheres to the standard.
//
//
// COMPARED TO C++11
//
// In C++11, you would implement this functionality using an r-value reference
// and our .Pass() method would be replaced with a call to std::move().
//
// This emulation also has a deficiency where it uses up the single
// user-defined conversion allowed by C++ during initialization.  This can
// cause problems in some API edge cases.  For instance, in scoped_ptr, it is
// impossible to make a function "void Foo(scoped_ptr<Parent> p)" accept a
// value of type scoped_ptr<Child> even if you add a constructor to
// scoped_ptr<> that would make it look like it should work.  C++11 does not
// have this deficiency.
//
//
// COMPARED TO Boost.Move
//
// Our implementation similar to Boost.Move, but we keep the RValue struct
// private to the move-only type, and we don't use the reinterpret_cast<> hack.
//
// In Boost.Move, RValue is the boost::rv<> template.  This type can be used
// when writing APIs like:
//
//   void MyFunc(boost::rv<Foo>& f)
//
// that can take advantage of rv<> to avoid extra copies of a type.  However you
// would still be able to call this version of MyFunc with an l-value:
//
//   Foo f;
//   MyFunc(f);  // Uh oh, we probably just destroyed |f| w/o calling Pass().
//
// unless someone is very careful to also declare a parallel override like:
//
//   void MyFunc(const Foo& f)
//
// that would catch the l-values first.  This was declared unsafe in C++11 and
// a C++11 compiler will explicitly fail MyFunc(f).  Unfortunately, we cannot
// ensure this in C++03.
//
// Since we have no need for writing such APIs yet, our implementation keeps
// RValue private and uses a .Pass() method to do the conversion instead of
// trying to write a version of "std::move()." Writing an API like std::move()
// would require the RValue struct to be public.
//
//
// CAVEATS
//
// If you include a move-only type as a field inside a class that does not
// explicitly declare a copy constructor, the containing class's implicit
// copy constructor will change from Containing(const Containing&) to
// Containing(Containing&).  This can cause some unexpected errors.
//
//   http://llvm.org/bugs/show_bug.cgi?id=11528
//
// The workaround is to explicitly declare your copy constructor.
//
#define TALK_MOVE_ONLY_TYPE_FOR_CPP_03(type, rvalue_type) \
 private: \
  struct rvalue_type { \
    explicit rvalue_type(type* object) : object(object) {} \
    type* object; \
    }; \
  type(type&); \
  void operator=(type&); \
 public: \
  operator rvalue_type() { return rvalue_type(this); } \
  type Pass() { return type(rvalue_type(this)); } \
 private:

#endif  // THIRD_PARTY_WEBRTC_FILES_TALK_BASE_MOVE_H_

/*
*  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
*
*  Use of this source code is governed by a BSD-style license
*  that can be found in the LICENSE file in the root of the source
*  tree. An additional intellectual property rights grant can be found
*  in the file PATENTS.  All contributing project authors may
*  be found in the AUTHORS file in the root of the source tree.
*/

// Borrowed from Chromium's src/base/basictypes.h.

#ifndef WEBRTC_SYSTEM_WRAPPERS_INTERFACE_COMPILE_ASSERT_H_
#define WEBRTC_SYSTEM_WRAPPERS_INTERFACE_COMPILE_ASSERT_H_

// The COMPILE_ASSERT macro can be used to verify that a compile time
// expression is true. For example, you could use it to verify the
// size of a static array:
//
//   COMPILE_ASSERT(ARRAYSIZE_UNSAFE(content_type_names) == CONTENT_NUM_TYPES,
//                  content_type_names_incorrect_size);
//
// or to make sure a struct is smaller than a certain size:
//
//   COMPILE_ASSERT(sizeof(foo) < 128, foo_too_large);
//
// The second argument to the macro is the name of the variable. If
// the expression is false, most compilers will issue a warning/error
// containing the name of the variable.

// TODO(ajm): Hack to avoid multiple definitions until the base/ of webrtc and
// libjingle are merged.
#if !defined(COMPILE_ASSERT)
template <bool>
struct CompileAssert {
};

#define COMPILE_ASSERT(expr, msg) \
  typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1]
#endif  // COMPILE_ASSERT


#endif  // WEBRTC_SYSTEM_WRAPPERS_INTERFACE_COMPILE_ASSERT_H_

#ifndef TALK_BASE_SCOPED_PTR_H__
#define TALK_BASE_SCOPED_PTR_H__

#include <cstddef>				// for std::ptrdiff_t
#include <stdlib.h>				// for free() decl
#include "compile_assert.h"		// for COMPILE_ASSERT
#include "move.h"				// for TALK_MOVE_ONLY_TYPE_FOR_CPP_03


#if !defined(WARN_UNUSED_RESULT)
#if defined(__GNUC__)
#define WARN_UNUSED_RESULT __attribute__((warn_unused_result))
#else
#define WARN_UNUSED_RESULT
#endif
#endif  // WARN_UNUSED_RESULT



#ifndef DISALLOW_ASSIGN
#define DISALLOW_ASSIGN(TypeName) \
  void operator=(const TypeName&)
#endif


#ifndef DISALLOW_COPY_AND_ASSIGN
// A macro to disallow the evil copy constructor and operator= functions
// This should be used in the private: declarations for a class
#define DISALLOW_COPY_AND_ASSIGN(TypeName)    \
  TypeName(const TypeName&);                    \
  DISALLOW_ASSIGN(TypeName)
#endif



#ifdef _WIN32
namespace std { using ::ptrdiff_t; };
#endif // _WIN32


namespace talk_base {



	// Function object which deletes its parameter, which must be a pointer.
	// If C is an array type, invokes 'delete[]' on the parameter; otherwise,
	// invokes 'delete'. The default deleter for scoped_ptr<T>.
	template <class T>
	struct DefaultDeleter {
		DefaultDeleter() {}
		template <typename U> DefaultDeleter(const DefaultDeleter<U>& other) {
			// IMPLEMENTATION NOTE: C++11 20.7.1.1.2p2 only provides this constructor
			// if U* is implicitly convertible to T* and U is not an array type.
			//
			// Correct implementation should use SFINAE to disable this
			// constructor. However, since there are no other 1-argument constructors,
			// using a COMPILE_ASSERT() based on is_convertible<> and requiring
			// complete types is simpler and will cause compile failures for equivalent
			// misuses.
			//
			// Note, the is_convertible<U*, T*> check also ensures that U is not an
			// array. T is guaranteed to be a non-array, so any U* where U is an array
			// cannot convert to T*.
			enum { T_must_be_complete = sizeof(T) };
			enum { U_must_be_complete = sizeof(U) };
			COMPILE_ASSERT((talk_base::is_convertible<U*, T*>::value),
				U_ptr_must_implicitly_convert_to_T_ptr);
		}
		inline void operator()(T* ptr) const {
			enum { type_must_be_complete = sizeof(T) };
			delete ptr;
		}
	};




	// Specialization of DefaultDeleter for array types.
	template <class T>
	struct DefaultDeleter<T[]> {
		inline void operator()(T* ptr) const {
			enum { type_must_be_complete = sizeof(T) };
			delete[] ptr;
		}

	private:
		// Disable this operator for any U != T because it is undefined to execute
		// an array delete when the static type of the array mismatches the dynamic
		// type.
		//
		// References:
		//   C++98 [expr.delete]p3
		//   http://cplusplus.github.com/LWG/lwg-defects.html#938
		template <typename U> void operator()(U* array) const;
	};


	template <class T, int n>
	struct DefaultDeleter<T[n]> {
		// Never allow someone to declare something like scoped_ptr<int[10]>.
		COMPILE_ASSERT(sizeof(T) == -1, do_not_use_array_with_size_as_type);
	};




	// Function object which invokes 'free' on its parameter, which must be
	// a pointer. Can be used to store malloc-allocated pointers in scoped_ptr:
	//
	// scoped_ptr<int, talk_base::FreeDeleter> foo_ptr(
	//     static_cast<int*>(malloc(sizeof(int))));
	struct FreeDeleter {
		inline void operator()(void* ptr) const {
			free(ptr);
		}
	};






	namespace internal {

		// Minimal implementation of the core logic of scoped_ptr, suitable for
		// reuse in both scoped_ptr and its specializations.
		template <class T, class D>
		class scoped_ptr_impl {
		public:
			explicit scoped_ptr_impl(T* p) : data_(p) { }

			// Initializer for deleters that have data parameters.
			scoped_ptr_impl(T* p, const D& d) : data_(p, d) {}

			// Templated constructor that destructively takes the value from another
			// scoped_ptr_impl.
			template <typename U, typename V>
			scoped_ptr_impl(scoped_ptr_impl<U, V>* other)
				: data_(other->release(), other->get_deleter()) {
				// We do not support move-only deleters.  We could modify our move
				// emulation to have talk_base::subtle::move() and
				// talk_base::subtle::forward()
				// functions that are imperfect emulations of their C++11 equivalents,
				// but until there's a requirement, just assume deleters are copyable.
			}

			template <typename U, typename V>
			void TakeState(scoped_ptr_impl<U, V>* other) {
				// See comment in templated constructor above regarding lack of support
				// for move-only deleters.
				reset(other->release());
				get_deleter() = other->get_deleter();
			}

			~scoped_ptr_impl() {
				if (data_.ptr != NULL) {
					// Not using get_deleter() saves one function call in non-optimized
					// builds.
					static_cast<D&>(data_)(data_.ptr);
				}
			}

			void reset(T* p) {
				// This is a self-reset, which is no longer allowed: http://crbug.com/162971
				if (p != NULL && p == data_.ptr)
					abort();

				// Note that running data_.ptr = p can lead to undefined behavior if
				// get_deleter()(get()) deletes this. In order to pevent this, reset()
				// should update the stored pointer before deleting its old value.
				//
				// However, changing reset() to use that behavior may cause current code to
				// break in unexpected ways. If the destruction of the owned object
				// dereferences the scoped_ptr when it is destroyed by a call to reset(),
				// then it will incorrectly dispatch calls to |p| rather than the original
				// value of |data_.ptr|.
				//
				// During the transition period, set the stored pointer to NULL while
				// deleting the object. Eventually, this safety check will be removed to
				// prevent the scenario initially described from occuring and
				// http://crbug.com/176091 can be closed.
				T* old = data_.ptr;
				data_.ptr = NULL;
				if (old != NULL)
					static_cast<D&>(data_)(old);
				data_.ptr = p;
			}

			T* get() const { return data_.ptr; }

			D& get_deleter() { return data_; }
			const D& get_deleter() const { return data_; }

			void swap(scoped_ptr_impl& p2) {
				// Standard swap idiom: 'using std::swap' ensures that std::swap is
				// present in the overload set, but we call swap unqualified so that
				// any more-specific overloads can be used, if available.
				using std::swap;
				swap(static_cast<D&>(data_), static_cast<D&>(p2.data_));
				swap(data_.ptr, p2.data_.ptr);
			}

			T* release() {
				T* old_ptr = data_.ptr;
				data_.ptr = NULL;
				return old_ptr;
			}

			T** accept() {
				reset(NULL);
				return &(data_.ptr);
			}

			T** use() {
				return &(data_.ptr);
			}

		private:
			// Needed to allow type-converting constructor.
			template <typename U, typename V> friend class scoped_ptr_impl;

			// Use the empty base class optimization to allow us to have a D
			// member, while avoiding any space overhead for it when D is an
			// empty class.  See e.g. http://www.cantrip.org/emptyopt.html for a good
			// discussion of this technique.
			struct Data : public D {
				explicit Data(T* ptr_in) : ptr(ptr_in) {}
				Data(T* ptr_in, const D& other) : D(other), ptr(ptr_in) {}
				T* ptr;
			};

			Data data_;

			DISALLOW_COPY_AND_ASSIGN(scoped_ptr_impl);
		};

	}  // namespace internal




	// A scoped_ptr<T> is like a T*, except that the destructor of scoped_ptr<T>
	// automatically deletes the pointer it holds (if any).
	// That is, scoped_ptr<T> owns the T object that it points to.
	// Like a T*, a scoped_ptr<T> may hold either NULL or a pointer to a T object.
	// Also like T*, scoped_ptr<T> is thread-compatible, and once you
	// dereference it, you get the thread safety guarantees of T.
	//
	// The size of scoped_ptr is small. On most compilers, when using the
	// DefaultDeleter, sizeof(scoped_ptr<T>) == sizeof(T*). Custom deleters will
	// increase the size proportional to whatever state they need to have. See
	// comments inside scoped_ptr_impl<> for details.
	//
	// Current implementation targets having a strict subset of  C++11's
	// unique_ptr<> features. Known deficiencies include not supporting move-only
	// deleteres, function pointers as deleters, and deleters with reference
	// types.
	template <class T, class D = talk_base::DefaultDeleter<T> >
	class scoped_ptr {
		TALK_MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue)

	public:
		// The element and deleter types.
		typedef T element_type;
		typedef D deleter_type;

		// Constructor.  Defaults to initializing with NULL.
		scoped_ptr() : impl_(NULL) { }

		// Constructor.  Takes ownership of p.
		explicit scoped_ptr(element_type* p) : impl_(p)
		{ 
		
		}

		// Constructor.  Allows initialization of a stateful deleter.
		scoped_ptr(element_type* p, const D& d) : impl_(p, d)
		{
		
		}

		// Constructor.  Allows construction from a scoped_ptr rvalue for a
		// convertible type and deleter.
		//
		// IMPLEMENTATION NOTE: C++11 unique_ptr<> keeps this constructor distinct
		// from the normal move constructor. By C++11 20.7.1.2.1.21, this constructor
		// has different post-conditions if D is a reference type. Since this
		// implementation does not support deleters with reference type,
		// we do not need a separate move constructor allowing us to avoid one
		// use of SFINAE. You only need to care about this if you modify the
		// implementation of scoped_ptr.
		template <typename U, typename V>
		scoped_ptr(scoped_ptr<U, V> other) : impl_(&other.impl_) {
			COMPILE_ASSERT(!talk_base::is_array<U>::value, U_cannot_be_an_array);
		}

		// Constructor.  Move constructor for C++03 move emulation of this type.
		scoped_ptr(RValue rvalue) : impl_(&rvalue.object->impl_) { }

		// operator=.  Allows assignment from a scoped_ptr rvalue for a convertible
		// type and deleter.
		//
		// IMPLEMENTATION NOTE: C++11 unique_ptr<> keeps this operator= distinct from
		// the normal move assignment operator. By C++11 20.7.1.2.3.4, this templated
		// form has different requirements on for move-only Deleters. Since this
		// implementation does not support move-only Deleters, we do not need a
		// separate move assignment operator allowing us to avoid one use of SFINAE.
		// You only need to care about this if you modify the implementation of
		// scoped_ptr.
		template <typename U, typename V>
		scoped_ptr& operator=(scoped_ptr<U, V> rhs) {
			COMPILE_ASSERT(!talk_base::is_array<U>::value, U_cannot_be_an_array);
			impl_.TakeState(&rhs.impl_);
			return *this;
		}

		// Reset.  Deletes the currently owned object, if any.
		// Then takes ownership of a new object, if given.
		void reset(element_type* p = NULL) { impl_.reset(p); }

		// Accessors to get the owned object.
		// operator* and operator-> will assert() if there is no current object.
		element_type& operator*() const {
			ASSERT(impl_.get() != NULL);
			return *impl_.get();
		}
		element_type* operator->() const  {
			ASSERT(impl_.get() != NULL);
			return impl_.get();
		}
		element_type* get() const { return impl_.get(); }

		// Access to the deleter.
		deleter_type& get_deleter() { return impl_.get_deleter(); }
		const deleter_type& get_deleter() const { return impl_.get_deleter(); }

		// Allow scoped_ptr<element_type> to be used in boolean expressions, but not
		// implicitly convertible to a real bool (which is dangerous).
		//
		// Note that this trick is only safe when the == and != operators
		// are declared explicitly, as otherwise "scoped_ptr1 ==
		// scoped_ptr2" will compile but do the wrong thing (i.e., convert
		// to Testable and then do the comparison).
	private:
		typedef talk_base::internal::scoped_ptr_impl<element_type, deleter_type>
			scoped_ptr::*Testable;

	public:
		operator Testable() const { return impl_.get() ? &scoped_ptr::impl_ : NULL; }

		// Comparison operators.
		// These return whether two scoped_ptr refer to the same object, not just to
		// two different but equal objects.
		bool operator==(const element_type* p) const { return impl_.get() == p; }
		bool operator!=(const element_type* p) const { return impl_.get() != p; }

		// Swap two scoped pointers.
		void swap(scoped_ptr& p2) {
			impl_.swap(p2.impl_);
		}

		// Release a pointer.
		// The return value is the current pointer held by this object.
		// If this object holds a NULL pointer, the return value is NULL.
		// After this operation, this object will hold a NULL pointer,
		// and will not own the object any more.
		element_type* release() WARN_UNUSED_RESULT{
			return impl_.release();
		}

			// Delete the currently held pointer and return a pointer
			// to allow overwriting of the current pointer address.
			element_type** accept() WARN_UNUSED_RESULT{
			return impl_.accept();
		}

			// Return a pointer to the current pointer address.
			element_type** use() WARN_UNUSED_RESULT{
			return impl_.use();
		}

			// C++98 doesn't support functions templates with default parameters which
			// makes it hard to write a PassAs() that understands converting the deleter
			// while preserving simple calling semantics.
			//
			// Until there is a use case for PassAs() with custom deleters, just ignore
			// the custom deleter.
			template <typename PassAsType>
		scoped_ptr<PassAsType> PassAs() {
			return scoped_ptr<PassAsType>(Pass());
		}

	private:
		// Needed to reach into |impl_| in the constructor.
		template <typename U, typename V> friend class scoped_ptr;
		talk_base::internal::scoped_ptr_impl<element_type, deleter_type> impl_;

		// Forbidden for API compatibility with std::unique_ptr.
		explicit scoped_ptr(int disallow_construction_from_null);

		// Forbid comparison of scoped_ptr types.  If U != T, it totally
		// doesn't make sense, and if U == T, it still doesn't make sense
		// because you should never have the same object owned by two different
		// scoped_ptrs.
		template <class U> bool operator==(scoped_ptr<U> const& p2) const;
		template <class U> bool operator!=(scoped_ptr<U> const& p2) const;
	};




}

#endif  // #ifndef TALK_BASE_SCOPED_PTR_H__

#include "scoped_ptr.h"
#include<iostream>
#include<string>



using namespace std;
using namespace talk_base;


class VoiceChannelTransport
{
public:
	VoiceChannelTransport(string s)
	{
		name = s;
	}
	void Initialize()
	{
		cout << "Initialize" << endl;
	}
private:
	string name;

};

int main()
{
	{
		talk_base::scoped_ptr<VoiceChannelTransport> voice_channel_transport(new VoiceChannelTransport("abc"));
		voice_channel_transport.reset(new VoiceChannelTransport("cde"));
		voice_channel_transport.get()->Initialize();
	}
	cout << "Initialize" << endl;
	//error 
	//VoiceChannelTransport * p = voice_channel_transport.get();
	//delete p;

	return 0;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值